Hook
在react conf 2018上,react发布了一个新的提案hook。那么这个hook到底是个什么呢,官方的定义是这样的
Hooks are a new feature proposal that lets you use state and other React features without writing a class.
那么hook是什么呢。他其实就是Class的另外一种写法。他最大的优点,就是以简洁的方式实现class能够实现的一切。给大家提供一种更简洁的写法。你用class写的模块,完全可以用hook来实现。虽然这么说,但不是说,对于老的系统,我们需要把所有的代码改造成hook。因为hook可以与class完美的共存。
函数式组件
hook起源于函数式组件,但在hook之前,函数式组件就一直存在。那么什么叫函数式组件呢,就是没有自己的state状态,所有的状态,都由父组件决定。
class写法
class Child extends Component{
render(){
//code
return (
<div>{this.props.count}</div>
)
}
}
函数式组件
function Child(props){
//code
return (
<div>{props.count}</div>
)
}
这就是所谓的函数式组件,他返回的内容就是render的内容。
useState
纯函数式组件,意义不大。因为我们大部分的组件,都是有状态的。比如说,一个tab组件,tabList,可以由父组件传递,但当前选中态,必须由自己来维护。useState的意义就是取代class组件中的this.state、this.setState、以及constructor生命周期
那么,这个tab组件的以下两种写法对比如下。
class
class Tab extends Component{
constructor(props){
super(props);
this.state = {
tabList : props.tabList
}
}
render(){
let {tabList} = this.state;
return (
<ul className='tab'>
{tabList.map((tab , key) =>
<li key={key} className={tab.active ? 'active' : ''} onClick={() => {
tabList.map((tab , index) => tab.active = key == index);
}}>
{tab.title}
</li>
)}
</ul>
)
}
}
hook
function Tab(props){
let [tabList , setTabList] = useState(props.tabList)
return (
<ul className='tab'>
{tabList.map((tab, key) =>
<li key={key} className={tab.active ? 'active' : ''} onClick={() => {
tabList.map((tab, index) => tab.active = key == index);
setTabList([...tabList]);
}}>
{tab.title}
</li>
)}
</ul>
)
}
useState 函数接收一个参数,表示的是tabList的默认值,组件每一次加载时,使用这个默认值,当我们点击tab时,切换会调用setTabList来更新组件状态,这时useState所返回的值,就不是默认值props.tabList,而是setTabList所传入的值。
useState 函数返回一个数组,里面包含两个对象。第一个是对象的值类型tabList,第二个是函数类型setTabList,这里通过es6的数组析构方式赋值。
只要我们调用setTabList,组件就会重新render
useState 的参数只会被调用一次,除了可以接收一个普通值或者对象外,还可以接收一个函数。比如,我们可以通过给他设置一个函数,来达到为Tab组件,设置默认选中第一个的需求。
let [tabList , setTabList] = useState(() => {
props.tabList[0].active = true;
return props.tabList;
})
注意:
hook的组件类型为纯组件,类似于PureComponent,而不是Component,对于setTabList的值,只有在旧对象 === 新对象才会重新render组件,因此我们使用[...tabList]方法去重新生成一个新的对象
useEffect
useEffect(function , array) 主要取代的是componentDidMount componentDidUpdate componentWillUnmount 三个生命周期函数。当然有同学会说,那还有componentWillMount呢。对于这个生命周期,由于,第一他的功能可以被constructor或者componentDidMount 所替代,第二,在React17版本即将被删除,因此,在此不多做叙述。
只加载一次的componentDidMount
还是以Tab 组件为例,如果,我们的tabList来源于服务端,而非父组件的话,我们可以使用 useEffect
function Tab(props) {
let [tabList, setTabList] = useState([])
useEffect(() => {
fetch(url).then(res => res.json())
.then(tabList => setTabList(tabList));
} , [])
}
我们注意到 useEffect后面,有一个空数组[]。它表示的意思是,组件第一次加载时,会执行useEffect中的方法,当下一次render的时候,如果数组中的值有变化,就继续执行useEffect中的方法,由于空数组永远不会有变化,因些他只执行一次,这样他就完美的替代了componentDidMount函数
注意:如果第二个参数不传,表示这样代码每次
render都会执行,这样,就失去了useEffect的意义
加载多次的compontDidUpdate
接下来,我们设计一个计数器功能,每次更新计数,我们都需要更新浏览器的标题
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
} , [count]);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
这个时候,我们第二参数可以使用 [count] , 当每次更改 count的时候,都会更新 document.title
需要清除的effect
那么,我们什么时候需要清除useEffect呢。举个例子,我们可以在组件加载时componentWillMount监听事件,然后在componentWillUnmount释放所监听事件,useEffect的清除,使用返回一个函数来处理,当组件卸载时,就会执行useEffect所返回的函数
function Example() {
useEffect(()=> {
event.on('事件' , ()=> {
//处理事件
});
return ()=> {
event.off('事件');
}
} , [])
}
性能优化之useMemo
我们知道react使用虚拟dom,大幅的提升了性能。但有的时候,我们仍然需要componentShouldUpdate,来决定组件,是否要进行虚拟dom的比对。比如下面这个页面
我们看到的是一个多标签的单页应用。上面每个tab代表一个页签,页签下面为页面。我们切换页面的时候,只是简单的将上一个页面display:none,然后将当前页面 display:block,如果我们每次切换,都需要完全比对虚拟dom的话,页面将会相当卡顿,因为页面会非常的多。但不幸的是按照react的原理,以及我们目前的架构,每次切换页面,都会完全虚拟DOM比对,那下我们怎么样去实现class类中的shouldComponentUpdate方法呢?
假如我们需要构造的页面组件为 <Page />
则可以通过如下代码
function MemoPage({ match, Page, name, configList}){
let memoPage = useMemo(() => <Page name={name} configList={configList} /> , []);
return (
<div style={{ display: match ? 'block' : 'none' }} className='main-page'>
{memoPage}
</div>
)
}
顾名思义,useMemo后面也有一个空数组,他的用法与useEffect一致,在这里表示组件,只构建一次。