React
推出后,前后出现了三种定义组件的方式,分别是:css
React.createClass
建立组件React.Component
建立组件相信你们在平常中使用的最多的仍是函数式组件和 React.Component
组件吧,今天就简单的说下函数式组件的两个优化方法。react
在谈到函数式组件以前咱们先看一个概念 - 纯函数
。数组
何为纯函数?dom
引用一段维基百科的概念。函数
在程序设计中,若一个函数符合如下要求,则它可能被认为是纯函数:性能
此函数在相同的输入值时,需产生相同的输出。函数的输出和输入值之外的其余隐藏信息或状态无关,也和由I/O设备产生的外部输出无关。测试
该函数不能有语义上可观察的函数反作用,诸如“触发事件”,使输出设备输出,或更改输出值之外物件的内容等。优化
能够看到,纯函数有着相同的输入一定产生相同的输出,没有反作用的特性。this
同理,函数式组件的输出也只依赖于 props
和 context
,与 state
无关。spa
this
(相信不少同窗被 this
烦过)state
)class
,没有 constructor
、extends
等代码,代码简洁,占用内存小。this
shouldComponentUpdate
,不能避免重复渲染。这里先看一个例子,代码以下,也可点击这里进行查看。
import * as React from "react";
import { render } from "react-dom";
import "./styles.css";
function App() {
const [n, setN] = React.useState(0);
const onClick = () => {
setN(n + 1);
};
return (
<div className="App">
<div>React.memo demo-1</div>
<button onClick={onClick}>update {n}</button>
<Child />
</div>
);
}
const Child = () => {
console.log("render child");
return <div className="child">Child Component</div>;
};
const rootElement = document.getElementById("root");
render(<App />, rootElement);
复制代码
它实现了什么?就一个很简单的东西,一个父组件包了一个子组件。
这个你们判断一下,当我点击父组件的 button
更新 N
的时候,子组件中的 log
会不会执行?
按照通常的思惟来看,你父组件更新关我子组件什么事?我子组件的 DOM
又不用更新,既然没有更新,那还打什么 log
。
但实际效果是,每点击一次 button
,子组件的 log
就会执行一次,虽然子组件的 DOM
没更新,但并不表明子组件的渲染函数没有执行。
如下是执行的效果图。
针对上述状况,class
组件可使用 shouldComponentUpdate
来进行优化,可是函数式组件呢?React
一样也提供了一个优化方法,那就是 React.memo
。
memo
即 memorized
,意思是记住。若是输入的参数没有变,依据纯函数的定义,输出也不会变,那么直接返回以前记忆的结果不就好了。
const Child = React.memo(() => {
console.log("render child");
return <div className="child">Child Component</div>;
});
复制代码
将 Child
使用 React.memo
处理一下,这样的话,不管你点击多少次父组件的 button
,子组件的 log
都不会执行。
完整代码点这里。
效果图以下:
咱们将上述代码稍微改一下, 让 Child
接受一个匿名函数,看看会产生什么后果。完整代码点这里。
import * as React from "react";
import { render } from "react-dom";
import "./styles.css";
function App() {
const [n, setN] = React.useState(0);
const onClick = () => {
setN(n + 1);
};
return (
<div className="App">
<div>React.useCallback demo-1</div>
<button onClick={onClick}>update {n}</button>
<Child onClick={() => {}} />
</div>
);
}
const Child = React.memo((props: { onClick: () => void }) => {
console.log("render child");
return <div className="child">Child Component</div>;
});
const rootElement = document.getElementById("root");
render(<App />, rootElement);
复制代码
观察代码能够看到也没啥变化嘛,只是子组件接受了一个空函数。
那么问题又来了,此次点击 button
子组件的 log
会执行吗?
看到这里各位同窗应该会想,每次都传一个空的匿名函数,props
也没变啊,那就不用从新渲染呗。具体结果如何,来看下效果:
能够看到每次点击 button
时,子组件的 log
依旧会再次执行。那么这是为何呢?
由于每次点击 button
更新父组件的时候,会从新生成一个空的匿名函数,虽然它们都是空的匿名函数,可是它们不是同一个函数。
函数是一个复杂类型的值,JavaScript
在比较复杂类型的值时,是对比它们的内存地址。不是同一个函数,那么内存地址也就不一样, React
会认为子组件的 props
发生了变化,子组件将从新渲染。
那么怎么保证子组件每次都接受同一个函数呢?
很简单。既然父组件在更新的时候会从新生成一个函数,那么我把函数放到父组件外面不就能够了嘛,这样父组件在更新的时候子组件就会接受同一个函数。
代码以下。也可点击这里查看。
import * as React from "react";
import { render } from "react-dom";
import "./styles.css";
const childOnClick = () => {};
function App() {
const [n, setN] = React.useState(0);
const onClick = () => {
setN(n + 1);
};
return (
<div className="App">
<div>React.useCallback demo-2</div>
<button onClick={onClick}>update {n}</button>
<Child onClick={childOnClick} />
</div>
);
}
const Child = React.memo((props: { onClick: () => void }) => {
console.log("render child");
return <div className="child">Child Component</div>;
});
const rootElement = document.getElementById("root");
render(<App />, rootElement);
复制代码
效果图以下:
这样子看起来好像解决了子组件每次都接受不一样的函数致使从新渲染的问题,可是好像哪里不对劲,实现也不优雅。
缺点
若是子组件的函数依赖父组件里面的值,那么这种方式就不可行。
怎么办呢?若是能将函数也 memorized
就行了。
React
在 16.8.0
的版本中正式推出了 Hooks
,其中有一个 Hook
叫作 useCallback
,它能将函数也 memorized
化。
useCallback
接受两个参数,第一个参数是一个函数,第二个参数是一个依赖数组,返回一个 memorized
后的函数。只有当依赖数组中的值发生了变化,它才会返回一个新函数。
看看使用 useCallback
后的代码,也能够点击这里查看。
import * as React from "react";
import { render } from "react-dom";
import "./styles.css";
function App() {
const [n, setN] = React.useState(0);
const [m, setM] = React.useState(0);
const childOnClick = React.useCallback(() => {
console.log(`m: ${m}`);
}, [m]);
return (
<div className="App">
<div>React.useCallback demo-3</div>
<button
onClick={() => {
setN(n + 1);
}}
>
update n: {n}
</button>
<button
onClick={() => {
setM(m + 1);
}}
>
update m: {m}
</button>
<Child onClick={childOnClick} />
</div>
);
}
const Child = React.memo((props: { onClick: () => void }) => {
console.log("render child");
return (
<div className="child">
<div>Child Component</div>
<button onClick={props.onClick}>log m</button>
</div>
);
});
const rootElement = document.getElementById("root");
render(<App />, rootElement);
复制代码
在上述代码中,子组件接受一个使用了 useCallback
的函数,它的依赖参数是 m
,只有当 m
发生了变化,子组件接受的函数才会是一个从新生成的函数。也就是说,不管点击多少次更新 n
的 button
,子组件都不会更新,只有点击更新 m
的 button
时,子组件才会更新。
看看效果如何:
实际效果符合咱们的预期。
看到这里有些同窗就会想了,若是使用 useCallback
的时候,传一个空数组做为依赖数组,那么子组件就再也不受父组件的影响了,即便你父组件的 m
变化了,我子组件依旧不会从新渲染,这样子岂不是性能更好?话很少说,咱们来测试一下就行了。代码点击这里查看,效果以下:
能够看到,虽然子组件确实没重复渲染了,但一样的也致使一个问题,打印出来的 m
永远都是 0,再也读取不到更新后的 m
的值。
由此能够得出结论,传一个空数组做为依赖数组的后果就是,子组件接受的函数里面的参数永远都是初始化使用 useCallback
时的值,这样的结果并非咱们想要的。
因此歪心思仍是少来了。
随着 React
正式推出 Hooks
,带来一系列新的特性,极大地加强了函数式组件的功能,利用这些新特性能够实现和 class
组件同样的效果。
有了 React Hooks
,咱们能够抛弃沉重的 class
组件,使用更加轻便,性能更加优异的函数式组件,所以掌握一些函数式组件的优化方法对咱们使用函数式组件开发是很是有用处的。
React Hooks
无论你香不香,反正我是先香了。
默默说一句,Vue 3.0
也会推出函数式组件。