如何正确应对提升state的层级导致过度渲染的情况
背景介绍
这是设计模式系列的第十一节,学习的是patterns.dev里设计模式中render函数作为props模式内容,由于是资料是英文版,所以我的学习笔记就带有翻译的性质,但并不是翻译,记录的是自己的学习过程和理解。
关于设计模式前九节的内容,在文末会有直达链接。
render函数作为props模式
极简释义:通过props或children传递render函数,动态渲染组件。
正文开始
<Title render={() => <h1>I am a render prop!</h1>} />
const Title = props => props.render();
在高阶组件一章,我们知道通过高阶组件,我们可以多组件复用数据或逻辑,另外一种实现方法就是传递render函数作为Props或children。
传递render函数这种模式就是给组件传递一个渲染函数,这个渲染函数返回JSX组件,而组件自己可以不渲染任何东西,只执行传递的render函数。
正如前面给到的示例Title组件传递一个render函数那样。
import React from "react";
import { render } from "react-dom";
import "./styles.css";
const Title = (props) => props.render();
render(
<div className="App">
<Title
render={() => (
<h1>
<span role="img" aria-label="emoji">
✨
</span>
I am a render prop!{" "}
<span role="img" aria-label="emoji">
✨
</span>
</h1>
)}
/>
</div>,
document.getElementById("root")
);
那么我们为什么要这样这样传递render函数呢?这样做的好处就是让Title组件具有很强的复用性,每次都可以传递不同的render,从而执行不同的渲染逻辑。
import React from "react";
import { render } from "react-dom";
import "./styles.css";
const Title = (props) => props.render();
render(
<div className="App">
<Title render={() => <h1>✨ First render prop! ✨</h1>} />
<Title render={() => <h2>🔥 Second render prop! 🔥</h2>} />
<Title render={() => <h3>🚀 Third render prop! 🚀</h3>} />
</div>,
document.getElementById("root")
);
当然render函数并一定需要命名为render,也可以同时传递多个render函数,同时进行渲染,像下面这样:
import React from "react";
import { render } from "react-dom";
import "./styles.css";
const Title = (props) => (
<>
{props.renderFirstComponent()}
{props.renderSecondComponent()}
{props.renderThirdComponent()}
</>
);
render(
<div className="App">
<Title
renderFirstComponent={() => <h1>✨ First render prop! ✨</h1>}
renderSecondComponent={() => <h2>🔥 Second render prop! 🔥</h2>}
renderThirdComponent={() => <h3>🚀 Third render prop! 🚀</h3>}
/>
</div>,
document.getElementById("root")
);
进阶用法
通常情况下使用render作为props的组件,不仅执行传递过来的render渲染JSX,还会有一些数据和逻辑处理:
function Component(props) {
const data = { ... }
return props.render(data)
}
<Component render={data => <ChildComponent data={data} />}
比如说上面这个例子,render函数还接受Component传递过来的data数据,然后再把数据转交给ChildComponet进行渲染。
案例分析
接下来,我们来看一个小小的案例:一个小应用,用户可以输入一个摄氏温度,我们将摄氏温度转换成里氏和标准温度,代码如下:
import React, { useState } from "react";
import "./styles.css";
function Input() {
const [value, setValue] = useState("");
return (
<input
type="text"
value={value}
onChange={e => setValue(e.target.value)}
placeholder="Temp in °C"
/>
);
}
export default function App() {
return (
<div className="App">
<h1>☃️ Temperature Converter 🌞</h1>
<Input />
<Kelvin />
<Fahrenheit />
</div>
);
}
function Kelvin({ value = 0 }) {
return <div className="temp">{value + 273.15}K</div>;
}
function Fahrenheit({ value = 0 }) {
return <div className="temp">{(value * 9) / 5 + 32}°F</div>;
}
上面的代码就遇到一个问题,输入框组件里的state温度值,同级的Kelvin和Fahrenheit组件拿不到值?
提升state层级
这时有一个常见的办法是,提升state层级到APP,传递state和setState方法给input输入框:
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>
);
}
虽然这是个可行的方法,但是在复杂的大型应用中,提升state的层级是一件危险的操作,而高层级的state变化会让该层级的所有子组件重新渲染,而那些不使用该state的组件就会过度渲染,从而影响性能。
有没有什么办法不提升state层级呢?这个时候就适合render作为props这种模式上场了
render函数作为props模式
由前文介绍,我们可以render函数作为props传递给组件。那么在这个案例中,我们是不是可以把 Kelvin和Fahrenheit这两个组件作为render传递给Input组件呢?
答案是当然的,这样就可以不用提升state层级,同时Input组件保持最大复用性:
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>
);
}
用render函数作为children
还有一种大家更常见的写法是把render函数作为children传递给组件,记得我在使用ant的组件和react-dnd时,就见过这种写法。
比如上面的例子,我们在Input组件的children里写render函数,在Input组件里调用children函数进行渲染:
export default function App() {
return (
<div className="App">
<h1>☃️ Temperature Converter 🌞</h1>
<Input>
{value => (
<>
<Kelvin value={value} />
<Fahrenheit value={value} />
</>
)}
</Input>
</div>
);
}
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)}
</>
);
}
总结
通过props或children传递render函数这种模式,和高阶组件模式一样,都解决多组件复用数据和逻辑问题,当然也包括增强组件复用性。并且在一些场景下,高阶组件可以替代传递render函数。但是相较于高阶组件模式,传递render函数模式有自己的优点:
- 传递render函数渲染这种模式,直接传递props,没有自动合并props,所以没有高阶组件的命名冲突覆盖问题;
- 数据更容易追溯源头,没有高阶组件的隐藏props的问题;
- 实现逻辑和视图渲染的分离;
当然传递render函数这种模式,在大部分场景下,可以被Hooks取代。当然如果在render函数里没有使用生命周期函数,并且不改变接受到的state,那么使用传递render函数模式也是很好的。
相关推荐
第一节:单例模式:高并发造成的数据统计困难?看我单例模式一招制敌
第二节:替身模式:JS和迪丽热巴一样有专业替身?没听过的快来补补课...
第三节:供应商模式:还在层层传递props?来学学非常实用的供应商模式吧
第四节:原型模式:都知道JavaScript原型,但设计模式里的原型模式你会用吗?
第五节:视图和逻辑分离模式:React Hooks时代,怎么实现视图与逻辑分离呢?
第六节:观察者模式:是时候拿出高级的技术了————观察者模式
第七节:模块化模式:前端性能优化进阶篇——动态加载模块基础补遗
第八节:混合模式:在React Hook时代,Object.assign这种混合写法还要用吗?
第九节:中间件模式:如何使用中间件优化多对多通信?
第十节:高阶组件模式:在React Hooks时代,高阶组件只能感叹:既生瑜何生亮?
相关活动
转载自:https://juejin.cn/post/7204373466191446072