render-props-pattern
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseRender Props Pattern
Render Props 模式
Another way of making components very reusable, is by using the render prop pattern. A render prop is a prop on a component, which value is a function that returns a JSX element. The component itself does not render anything besides the render prop. Instead, the component simply calls the render prop, instead of implementing its own rendering logic.
Imagine that we have a component. In this case, the component shouldn't do anything besides rendering the value that we pass. We can use a render prop for this! Let's pass the value that we want the component to render to the prop.
TitleTitleTitlerender让组件具备高度复用性的另一种方式是使用render prop模式。Render prop是组件上的一个prop,其值是一个返回JSX元素的函数。该组件本身除了render prop外不渲染任何内容,而是直接调用render prop,而非实现自身的渲染逻辑。
假设我们有一个组件,在这种情况下,组件除了渲染我们传递的值之外不应该做任何其他事情。我们可以用render prop来实现这一点!将我们希望组件渲染的值传递给 prop即可。
TitleTitleTitlerenderWhen to Use
适用场景
- Use this when you need to share stateful logic between components with different rendering needs
- This is helpful when the HOC pattern creates naming collision issues or overly deep nesting
- 当你需要在具有不同渲染需求的组件之间共享有状态逻辑时使用
- 当HOC模式导致命名冲突或嵌套过深问题时,这种方式会很有帮助
Instructions
使用指南
- Pass a function as a prop (or
renderprop) that receives data and returns JSXchildren - Prefer custom Hooks over render props in most modern React code
- Use the children-as-a-function pattern as a cleaner alternative to explicit props
render - Avoid deeply nesting multiple render prop components — refactor to Hooks instead
- 传递一个函数作为prop(或
renderprop),该函数接收数据并返回JSXchildren - 在大多数现代React代码中,优先使用自定义Hooks而非render props
- 使用“子组件作为函数”模式作为显式prop的更简洁替代方案
render - 避免深度嵌套多个render prop组件——改用Hooks重构
Details
详细说明
js
<Title render={() => <h1>I am a render prop!</h1>} />Within the component, we can render this data by returning the invoked prop!
Titlerenderjs
const Title = (props) => props.render();Although they're called render props, a render prop doesn't have to be called . Any prop that renders JSX is considered a render prop!
renderA component that takes a render prop usually does a lot more than simply invoking the prop. Instead, we usually want to pass data from the component that takes the render prop, to the element that we pass as a render prop!
renderjs
function Component(props) {
const data = { ... }
return props.render(data)
}The render prop can now receive this value that we passed as its argument.
js
<Component render={data => <ChildComponent data={data} />}Let's look at an example! We have a simple app, where a user can type a temperature in Celsius. The app shows the value of this temperature in Fahrenheit and Kelvin.
Currently there's a problem. The stateful component contains the value of the user's input, meaning that the and component don't have access to the user's input!
InputFahrenheitKelvinjs
<Title render={() => <h1>I am a render prop!</h1>} />在组件内部,我们可以通过调用 prop来渲染这个数据!
Titlerenderjs
const Title = (props) => props.render();尽管它们被称为_render_ props,但render prop不一定非要命名为。任何用于渲染JSX的prop都可被视为render prop!
render接收render prop的组件通常不仅仅是调用 prop,我们通常希望将接收render prop的组件中的数据传递给作为render prop传入的元素!
renderjs
function Component(props) {
const data = { ... }
return props.render(data)
}现在,render prop可以接收我们作为参数传递的这个值。
js
<Component render={data => <ChildComponent data={data} />}让我们看一个示例!我们有一个简单的应用,用户可以输入摄氏温度,应用会将该温度值转换为华氏温度和开尔文温度显示出来。
目前存在一个问题:有状态的组件存储了用户的输入值,这意味着和组件无法访问用户的输入!
InputFahrenheitKelvinLifting state
状态提升
One way to make the users input available to both the and component is to lift the state.
FahrenheitKelvinIn this case, we have a stateful component. However, the sibling components and also need access to this data. Instead of having a stateful component, we can lift the state up to the first common ancestor component that has a connection to , and : the component in this case!
InputFahrenheitKelvinInputInputFahrenheitKelvinAppjs
function Input({ value, handleChange }) {
return <input value={value} onChange={(e) => handleChange(e.target.value)} />;
}
export default function App() {
const [value, setValue] = useState("");
return (
<div className="App">
<h1>☃️ Temperature Converter 🌞</h1>
<Input value={value} handleChange={setValue} />
<Kelvin value={value} />
<Fahrenheit value={value} />
</div>
);
}Although this is a valid solution, it can be tricky to lift state in larger applications with components that handle many children. Each state change could cause a re-render of all the children, even the ones that don't handle the data, which could negatively affect the performance of your app.
让用户输入对和组件都可用的一种方法是进行状态提升。
FahrenheitKelvin在这个案例中,我们有一个有状态的组件,但同级的和组件也需要访问这些数据。我们可以将状态提升到与、和都有连接的第一个共同祖先组件——在这个案例中是组件,而非让组件持有状态!
InputFahrenheitKelvinInputFahrenheitKelvinAppInputjs
function Input({ value, handleChange }) {
return <input value={value} onChange={(e) => handleChange(e.target.value)} />;
}
export default function App() {
const [value, setValue] = useState("");
return (
<div className="App">
<h1>☃️ 温度转换器 🌞</h1>
<Input value={value} handleChange={setValue} />
<Kelvin value={value} />
<Fahrenheit value={value} />
</div>
);
}虽然这是一个有效的解决方案,但在包含多个子组件的大型应用中进行状态提升可能会很棘手。每次状态变化都可能导致所有子组件重新渲染,即使是那些不处理数据的组件,这可能会对应用性能产生负面影响。
Render props
Render Props
Instead, we can use render props! Let's change the component in a way that it can receive render props.
Inputjs
function Input(props) {
const [value, setValue] = useState("");
return (
<>
<input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder="Temp in °C"
/>
{props.render(value)}
</>
);
}
export default function App() {
return (
<div className="App">
<h1>☃️ Temperature Converter 🌞</h1>
<Input
render={(value) => (
<>
<Kelvin value={value} />
<Fahrenheit value={value} />
</>
)}
/>
</div>
);
}Perfect, the and components now have access to the value of the user's input!
KelvinFahrenheit相反,我们可以使用render props!让我们修改组件,使其能够接收render props。
Inputjs
function Input(props) {
const [value, setValue] = useState("");
return (
<>
<input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder="Temp in °C"
/>
{props.render(value)}
</>
);
}
export default function App() {
return (
<div className="App">
<h1>☃️ 温度转换器 🌞</h1>
<Input
render={(value) => (
<>
<Kelvin value={value} />
<Fahrenheit value={value} />
</>
)}
/>
</div>
);
}完美!现在和组件可以访问用户的输入值了!
KelvinFahrenheitChildren as a function
子组件作为函数
Besides regular JSX components, we can pass functions as children to React components. This function is available to us through the prop, which is technically also a render prop.
childrenLet's change the component. Instead of explicitly passing the prop, we'll just pass a function as a child for the component.
InputrenderInputjs
export default function App() {
return (
<div className="App">
<h1>☃️ Temperature Converter 🌞</h1>
<Input>
{(value) => (
<>
<Kelvin value={value} />
<Fahrenheit value={value} />
</>
)}
</Input>
</div>
);
}We have access to this function, through the prop that's available on the component. Instead of calling with the value of the user input, we'll call with the value of the user input.
props.childrenInputprops.renderprops.childrenjs
function Input(props) {
const [value, setValue] = useState("");
return (
<>
<input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder="Temp in °C"
/>
{props.children(value)}
</>
);
}Great, this way the and component have access to the value, without having to worry about the name of the prop.
KelvinFahrenheitrender除了常规的JSX组件,我们还可以将函数作为子组件传递给React组件。这个函数可以通过 prop访问,从技术上讲这也是一种render prop。
children让我们修改组件,不再显式传递 prop,而是直接将一个函数作为组件的子组件传递。
InputrenderInputjs
export default function App() {
return (
<div className="App">
<h1>☃️ 温度转换器 🌞</h1>
<Input>
{(value) => (
<>
<Kelvin value={value} />
<Fahrenheit value={value} />
</>
)}
</Input>
</div>
);
}我们可以通过组件上的 prop访问这个函数。不再调用并传入用户输入的值,而是调用并传入用户输入的值。
Inputprops.childrenprops.renderprops.childrenjs
function Input(props) {
const [value, setValue] = useState("");
return (
<>
<input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder="Temp in °C"
/>
{props.children(value)}
</>
);
}太棒了!这样和组件就可以访问该值,而无需担心 prop的名称。
KelvinFahrenheitrenderHooks
Hooks
In some cases, we can replace render props with Hooks. A good example of this is Apollo Client.
One way to use Apollo Client is through the and components. In order to pass data down from the component to the elements that need the data, we pass a function as a child. The function receives the value of the data through its arguments.
MutationQueryMutationjs
<Mutation mutation={...} variables={...}>
{addMessage => <div className="input-row">...</div>}
</Mutation>Although we can still use the render prop pattern and is often preferred compared to the higher order component pattern, it has its downsides.
One of the downsides is deep component nesting. We can nest multiple or components, if a component needs access to multiple mutations or queries.
MutationQueryjs
<Mutation mutation={FIRST_MUTATION}>
{(firstMutation) => (
<Mutation mutation={SECOND_MUTATION}>
{(secondMutation) => (
<Mutation mutation={THIRD_MUTATION}>
{(thirdMutation) => (
<Element
firstMutation={firstMutation}
secondMutation={secondMutation}
thirdMutation={thirdMutation}
/>
)}
</Mutation>
)}
</Mutation>
)}
</Mutation>After the release of Hooks, Apollo added Hooks support to the Apollo Client library. Instead of using the and render props, developers can now directly access the data through the hooks that the library provides.
MutationQueryBy using the hook, we reduced the amount of code that was needed in order to provide the data to the component.
useQuery在某些情况下,我们可以用Hooks替代render props。Apollo Client就是一个很好的例子。
使用Apollo Client的一种方式是通过和组件。为了将数据从组件传递给需要该数据的元素,我们将一个函数作为子组件传递,该函数通过参数接收数据的值。
MutationQueryMutationjs
<Mutation mutation={...} variables={...}>
{addMessage => <div className="input-row">...</div>}
</Mutation>尽管我们仍然可以使用render prop模式,并且它通常比高阶组件(HOC)模式更受欢迎,但它也有缺点。
缺点之一是组件深度嵌套。如果一个组件需要访问多个mutation或query,我们可能会嵌套多个或组件。
MutationQueryjs
<Mutation mutation={FIRST_MUTATION}>
{(firstMutation) => (
<Mutation mutation={SECOND_MUTATION}>
{(secondMutation) => (
<Mutation mutation={THIRD_MUTATION}>
{(thirdMutation) => (
<Element
firstMutation={firstMutation}
secondMutation={secondMutation}
thirdMutation={thirdMutation}
/>
)}
</Mutation>
)}
</Mutation>
)}
</Mutation>Hooks发布后,Apollo为Apollo Client库添加了Hooks支持。开发者现在可以通过库提供的Hooks直接访问数据,而无需使用和的render props。
MutationQuery通过使用 Hook,我们减少了为组件提供数据所需的代码量。
useQueryPros
优点
Sharing logic and data among several components is easy with the render props pattern. Components can be made very reusable, by using a render or prop. Although the Higher Order Component pattern mainly solves the same issues, namely reusability and sharing data, the render props pattern solves some of the issues we could encounter by using the HOC pattern.
childrenThe issue of naming collisions that we can run into by using the HOC pattern no longer applies by using the render props pattern, since we don't automatically merge props. We explicitly pass the props down to the child components, with the value provided by the parent component.
Since we explicitly pass props, we solve the HOC's implicit props issue. The props that should get passed down to the element, are all visible in the render prop's arguments list. This way, we know exactly where certain props come from.
We can separate our app's logic from rendering components through render props. The stateful component that receives a render prop can pass the data onto stateless components, which merely render the data.
使用render props模式可以轻松地在多个组件之间共享逻辑和数据。通过使用render或 prop,组件可以具备极高的复用性。尽管高阶组件(HOC)模式主要解决的是相同的问题,即复用性和数据共享,但render props模式解决了我们使用HOC模式时可能遇到的一些问题。
children使用HOC模式时可能遇到的命名冲突问题在使用render props模式时不再存在,因为我们不会自动合并props。我们会显式地将props传递给子组件,值由父组件提供。
由于我们显式传递props,解决了HOC的隐式props问题。需要传递给元素的props都清晰地显示在render prop的参数列表中,这样我们就能确切知道某些props的来源。
通过render props,我们可以将应用的逻辑与渲染组件分离。接收render prop的有状态组件可以将数据传递给仅负责渲染数据的无状态组件。
Cons
缺点
The issues that we tried to solve with render props, have largely been replaced by React Hooks. As Hooks changed the way we can add reusability and data sharing to components, they can replace the render props pattern in many cases.
Since we can't add lifecycle methods to a prop, we can only use it on components that don't need to alter the data they receive.
renderNote (React 18+): The render props pattern is now largely supplanted by Hooks in React's best practices. Render props often resulted in deeply nested JSX "callback hell" — for example, nesting multiplecomponents to get multiple pieces of data. Modern libraries like Apollo Client now provide Hooks (e.g.,<Mutation>,useMutation) that allow you to fetch or compute needed data inside the component, eliminating the need for wrapper components. Hooks don't create new component boundaries, so state can be shared more directly and the React Compiler can statically analyze the code more easily. While render props are still possible, if you find yourself writing a component whose sole purpose is to calluseQueryor use children-as-a-function, ask if a custom Hook could achieve the same result more directly.props.render()
我们试图用render props解决的问题,很大程度上已经被React Hooks取代。由于Hooks改变了我们为组件添加复用性和数据共享的方式,在许多情况下它们可以替代render props模式。
由于我们无法为 prop添加生命周期方法,因此它只能用于不需要修改接收数据的组件。
render注意(React 18+): 在React的最佳实践中,render props模式现在已基本被Hooks取代。Render props常常导致JSX深度嵌套的“回调地狱”——例如,嵌套多个组件以获取多份数据。像Apollo Client这样的现代库现在提供了Hooks(如<Mutation>、useMutation),允许你在组件内部直接获取或计算所需数据,从而无需使用包装组件。Hooks不会创建新的组件边界,因此状态可以更直接地共享,React编译器也可以更轻松地对代码进行静态分析。虽然仍然可以使用render props,但如果你发现自己编写的组件唯一作用是调用useQuery或使用“子组件作为函数”,不妨思考一下自定义Hook是否能更直接地实现相同的效果。props.render()