拥有了hooks,你再也不需要写Class了,你的所有组件都将是Function
class版
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}
使用hook后
import { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
组件变成了一个函数,但这个函数却有自己的状态(count),同时它还可以更新自己的状态(setCount)。这个函数之所以这么了不得,就是因为它注入了一个hook--useState
,就是这个hook让我们的函数变成了一个有状态的函数
Hooks本质上就是一类特殊的函数,它们可以为你的函数型组件(function component)注入一些特殊的功能
React为什么要搞一个Hooks?
1.想要复用一个有状态的组件太麻烦了!
我们都知道react都核心思想就是,将一个页面拆成一堆独立的,可复用的组件,并且用自上而下的单向数据流的形式将这些组件串联起来。但假如你在大型的工作项目中用react,你会发现你的项目中实际上很多react组件冗长且难以复用。尤其是那些写成class的组件,它们本身包含了状态(state),所以复用这类组件就变得很麻烦。
那之前,官方推荐怎么解决这个问题呢
?答案是:渲染属性
(Render Props)和高阶组件
(Higher-Order Components)。 这两种模式看上去都挺不错的,很多库也运用了这种模式,比如我们常用的React Router。但我们仔细看这两种模式,会发现它们会增加我们代码的层级关系。
2.生命周期钩子函数里的逻辑太乱!
我们通常希望一个函数只做一件事情,但我们的生命周期钩子函数里通常同时做了很多事情。比如我们需要在componentDidMount
中发起ajax请求获取数据,绑定一些事件监听等等。同时,有时候我们还需要在componentDidUpdate
做一遍同样的事情。当项目变复杂后,这一块的代码也变得不那么直观。
3.this指向
为了保证this的指向正确,我们要经常写这样的代码:this.handleClick = this.handleClick.bind(this)
,或者是这样的代码:<button onClick={() => this.handleClick(e)}>
。
什么是State Hooks
useState
是react自带的一个hook函数,它的作用就是用来声明状态变量。useState这个函数接收的参数是我们的状态初始值(initial state),它返回了一个数组,这个数组的第[0]
项是当前当前的状态值,第[1]
项是可以改变状态值的方法函数。
react规定我们必须把hooks写在函数的最外层,不能写在ifelse等条件语句当中,来确保hooks的执行顺序一致
let showFruit = true;
function ExampleWithManyStates() {
const [age, setAge] = useState(42);
if(showFruit) {
const [fruit, setFruit] = useState('banana');
showFruit = false;
}
const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
}
//第一次渲染
useState(42); //将age初始化为42
useState('banana'); //将fruit初始化为banana
useState([{ text: 'Learn Hooks' }]); //...
//第二次渲染
useState(42); //读取状态变量age的值(这时候传的参数42直接被忽略)
// useState('banana');
useState([{ text: 'Learn Hooks' }]); //读取到的却是状态变量fruit的值,导致报错
注意
:每次渲染都会有自己独立的props和state
什么是Effect Hooks
Effect Hook 可以让你能够在 Function 组件中执行副作用(side effects):
import { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// 类似于componentDidMount 和 componentDidUpdate:
useEffect(() => {
// 更新文档的标题
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
第一,react首次渲染和之后的每次渲染都会调用一遍传给useEffect
的函数。而之前我们要用两个声明周期函数来分别表示首次渲染(componentDidMount)
,和之后的更新导致的重新渲染(componentDidUpdate)
。
第二,useEffect中定义的副作用函数的执行不会阻碍浏览器更新视图
,也就是说这些函数是异步执行
的,而之前的componentDidMount
或componentDidUpdate
中的代码则是同步执行
的。这种安排对大多数副作用说都是合理的,但有的情况除外,比如我们有时候需要先根据DOM计算出某个元素的尺寸再重新渲染,这时候我们希望这次重新渲染是同步发生的,也就是说它会在浏览器真的去绘制这个页面前发生。
怎么跳过一些不必要的副作用函数
我们只需要给useEffect传第二个参数即可。用第二个参数来告诉react只有当这个参数的值发生改变时,才执行我们传的副作用函数(第一个参数)
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // 只有当count的值发生变化时,才会重新执行`document.title`这一句
当我们第二个参数传一个空数组[]
时,其实就相当于只在首次渲染的时候执行。也就是componentDidMount加componentWillUnmount的模式。不过这种用法可能带来bug
模拟场景
componentDidMount、componentDidUpdate
import React,{useEffect} from 'react'
useEffect(()=>{
//每次componentDidMount、componentDidUpdate都会调用这儿
})
componentDidMount 加第二个参数[]
useEffect(()=>{
//componentDidMount执行
},[])
componentDidUnMount 加第二个参数[]
useEffect(()=>{
//componentDidMount执行
return ()=>{
//componentDidUnMount执行
}
},[])
React.memo和useMemo 减少不必要的渲染
React.momo其实并不是一个hook,它其实等价于PureComponent
,但是它只会对比props
import React, { useState } from 'react';
export const Count = React.memo((props) => {
const [data, setData] = useState({
count: 0,
age: 18,
});
const handleClick = () => {
const { count } = data;
setData({
...data,
count: count + 1,
})
};
return (<button onClick={handleClick}>count:{data.count}</button>)
});