在咱们日常的 React 开发中,咱们肆无忌惮的编写着代码,项目刚开始可能没什么大问题,可是随着项目愈来愈大,功能愈来愈多,问题就慢慢的就凸显出来了。为了让项目可以正常、稳定的运行,咱们在编写代码的时候应该多思考,代码该怎么划分、该怎么编写。javascript
如下为总结的一些关于react优化相关的知识:java
React.PureComponent
和React.memo
很类似,都是可以在必定的条件下减小组件的从新渲染,提升性能。react
React.PureComponent
与React.Component
很类似,二者的区别在于 React.Component
并未实现shouldComponentUpdate()
,而React.PureComponent
中以浅层对比 prop 和 state 的方式来实现了shouldComponentUpdate()
,因此当新的 props 或 state 出现的时候,React.PureComponent
会自行将新的 props 和 state 与原来的进行浅对比,若是没有变化的话,组件就不会从新render。ios
注意
React.PureComponent
仅仅是作浅对比,若是 props 或者 state 有比较复杂的结构好比数组或者对象的话,这个时候比较容易出错,须要你在深层数据结构发生变化时调用forceUpdate()
来确保组件被正确地更新。你也能够考虑使用 immutable 对象加速嵌套数据的比较。
React.PureComponent
中若是编写了自定义的shouldComponentUpdate()
,React.PureComponent
将会取消默认的浅层对比,而使用自定义的shouldComponentUpdate()
。算法
class RegularChildComponent extends Component<IProps, IState> {
render() {
console.log("Regular Component Rendered.."); // 每次父组件更新都会渲染
return <div>{this.props.name}</div>;
}
}
class PureChildComponent extends PureComponent<IProps, IState> {
readonly state: Readonly<IState> = {
age: "18",
}
updateState = () => {
setInterval(() => {
this.setState({
age: "18"
})
}, 1000)
}
componentDidMount() {
this.updateState();
}
render() {
console.log("Pure Component Rendered..") // 只渲染一次
return <div>{this.props.name}</div>;
}
}
class Demo extends Component<IProps, IState> {
readonly state: Readonly<IState> = {
name: "liu",
}
componentDidMount() {
this.updateState();
}
updateState = () => {
setInterval(() => {
this.setState({
name: "liu"
})
}, 1000)
}
render() {
console.log("Render Called Again") // 每次组件更新都会渲染
return (
<div>
<RegularChildComponent name={'this.state.name'} />
<PureChildComponent name={this.state.name} />
</div>
)
}
}
复制代码
React.memo
和React.PureComponent
功能差很少,可是React.memo
适用于函数组件,但不适用于 class 组件。数组
若是你的函数组件在给定相同 props 的状况下渲染相同的结果,那么你能够经过将其包装在React.memo
中调用,以此经过记忆组件渲染结果的方式来提升组件的性能表现。这意味着在这种状况下,React 将跳过渲染组件的操做并直接复用最近一次渲染的结果。数据结构
默认状况下其只会对复杂对象作浅层对比,若是你想要控制对比过程,那么请将自定义的比较函数经过第二个参数传入来实现。函数
// 父组件和上面同样
function ChildComponent(props: IProps){
console.log("Pure Component Rendered..") // 只渲染一次
return <div>{props.name}</div>;
}
const PureChildComponent = React.memo(ChildComponent);
复制代码
在这个函数里面,咱们能够本身来决定是否从新渲染组件,返回 false 以告知 React 能够跳过更新。首次渲染或使用 forceUpdate() 时不会调用该方法。性能
注意 不建议在 shouldComponentUpdate() 中进行深层比较或使用 JSON.stringify()。这样很是影响效率,且会损害性能。
返回 false 并不会阻止子组件在 state 更改时从新渲染。优化
class Demo extends Component<IProps, IState> {
readonly state: Readonly<IState> = {
name: "liu",
age: 18,
}
componentDidMount() {
this.setState({
name: "liu",
age: 19,
})
}
shouldComponentUpdate(nextProps: IProps, nextState: IState){
if(this.state.name !== nextState.name){
return true;
}
return false;
}
render() {
console.log("Render Called Again") // 只会打印一次
return (
<div> {this.state.name} </div>
)
}
}
复制代码
上面的例子中,由于组件只渲染了 state.name ,因此当 age 改变可是 name 没有改变的时候咱们并不须要从新渲染,因此咱们能够在shouldComponentUpdate()
中阻止渲染。在上面例子中阻止渲染是对的,可是当前组件或子组件若是用到了 state.age ,那么咱们就不能只根据 name 来判断是否阻止渲染,不然会出现数据和界面不一致的状况。
所以,当咱们在使用shouldComponentUpdate()
应该始终清楚咱们什么状况下才能阻止从新渲染。
当咱们在 React 中建立函数时,咱们须要使用 bind 关键字将函数绑定到当前上下文。绑定能够在构造函数中完成,也能够在咱们将函数绑定到 DOM 元素的位置上完成。
可是当咱们将函数绑定到 DOM 元素的位置后,每次render的时候都会进行一次bind,这将会有一些没必要要的性能损耗,并且还有可能致使子组件没必要要的渲染。因此咱们能够在构造函数中绑定,也能够直接写箭头函数。同理,咱们尽可能不写内联函数和内联属性。
// good
class Binding extends React.Component<IProps, IState> {
constructor() {
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
alert("Button Clicked")
}
render() {
return (
<input type="button" value="Click" onClick={this.handleClick} />
)
}
}
// good
class Binding extends React.Component<IProps, IState> {
handleClick = () => {
alert("Button Clicked")
}
render() {
return (
<input type="button" value="Click" onClick={this.handleClick} />
)
}
}
// bad
class Binding extends React.Component<IProps, IState> {
handleClick() {
alert("Button Clicked")
}
render() {
return (
<input type="button" value="Click" onClick={this.handleClick.bind(this)} />
)
}
}
复制代码
key 帮助 React 识别哪些元素改变了,好比被添加或删除。所以你应当给数组中的每个元素赋予一个肯定的标识。由于有key的存在,使得 React 的diff算法有质的飞跃。
一个元素的 key 最好是这个元素在列表中拥有的一个独一无二的字符串。一般,咱们使用数据中的 id 来做为元素的 key。若是列表项目的顺序可能会变化,咱们不建议使用索引来用做 key 值,由于这样作会致使性能变差,还可能引发组件状态的问题。
const todoItems = todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
);
复制代码
当咱们在一个界面有两个或多个互斥或者不太可能同时出现的”大组件“的时候,咱们能够将那些组件分割出来,以减小主js的体积。
// 该组件是动态加载的
const OneComponent = React.lazy(() => import('./OneComponent'));
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
let isIOS = getCurrentEnv();
return (
// 显示 <Spinner> 组件直至 OneComponent 或 OtherComponent 加载完成
<React.Suspense fallback={<Spinner />}>
<div>
{
isIOS ? <OneComponent /> : <OtherComponent />>
}
</div>
</React.Suspense>
);
}
复制代码
上面的代码示例中, OneComponent 和 OtherComponent 是两个比较大的组件,在ios环境下咱们渲染 OneComponent,在非ios环境下渲染 OtherComponent,正常状况下两个组件只会被加载其中的一个,因此咱们能够将代码分割出来,保证主js不会包括不须要用到的js。
在大多数状况下,咱们也会将代码以路由为界限进行分割,来提高界面首次加载速度。