前言:Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
constructor
:函数组件不需要构造函数。你可以通过调用 useState 来初始化 state。如果计算的代价比较昂贵,你可以传一个函数给 useState。getDerivedStateFromProps
:改为 在渲染时 安排一次更新。shouldComponentUpdate
: 使用React.memo(component)
函数包裹即可。render
: 函数组件体本身就是个rendercomponentDidMount
,componentDidUpdate
, componentWillUnmount
: useEffect Hook 可以表达所有这些。getSnapshotBeforeUpdate
,componentDidCatch
,getDerivedStateFromError
:目前还没有这些对应hooks写法,后续版本会添加react官方发布了一个eslint-plugin-react-hooks
的eslint插件来检测规则。后续版本可能会集成到cli中。
npm install eslint-plugin-react-hooks --save-dev
{
//你的 ESLint 配置
"plugins": [
// ...
"react-hooks"
],
"rules": {
// ...
"react-hooks/rules-of-hooks": "error", // 检查 Hook 的规则
"react-hooks/exhaustive-deps": "warn" // 检查 effect 的依赖
}
}
eg:
import React, {
useState } from 'react'; //引入useState函数
function component() {
// 声明一个叫 "count" 的 state 变量
const [count, setCount] = useState(0); //返回了2个元素的数组值,第一个是state,第二个是改变state的方法
return (
<div>
<p>You clicked {
count} times</p>
<button onClick={
() => setCount(count + 1)}> //改变count的状态
Click me
</button>
</div>
);
}
state 初始值为 { count: 0 }
,调用setCount(count+1)
就可以实现状态更新。
如果你熟悉 React class 的生命周期函数,你可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。
eg:
import React, {
useState, useEffect } from 'react'; //引入useEffect函数
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
//componentDidMount 和 componentDidUpdate 会调用
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
//每次 重新渲染 时都会被调用,以清除副作用代码, 比如定时器
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
state初始值{isOnline:null}
,friend.id
改变时在线状态会进行重置,cleanup
会在组件销毁时清除订阅,以消除副作用代码。useEffect 默认会在调用一个新的 effect 之前对前一个 effect 进行清理,也就是组件重新渲染的时候就会进行清理,如果设置了return
。
在某些情况下,每次渲染后都执行清理或者执行 effect 可能会导致性能问题。只要传递数组作为 useEffect 的第二个可选参数即可:
eg:
useEffect(() => {
document.title = `You clicked ${
props.count} times`;
}, [props.count]); // 仅在 count 更改时更新
小提示:如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([])作为第二个参数。这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行。
如果你在接触 Hook 前已经对 context API 比较熟悉,useContext(MyContext) 就相当于 class 组件中的 static contextType = MyContext 或者
。
eg:
const themes = {
//声明全局state
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
const ThemeContext = React.createContext(themes.light); //创建context组件,并设置默认值
function App() {
return (
<ThemeContext.Provider value={
themes.dark}> //定义provider,传入主题对象
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
const theme = useContext(ThemeContext); //声明使用context
return (
<button style={
{
background: theme.background, color: theme.foreground }}>
I am styled by theme context!
</button>
);
}
function useFriendStatus(friendID) {
//声明自定义hook
const [isOnline, setIsOnline] = useState(null);
// ...
return isOnline;
}
function FriendStatus(props) {
const isOnline = useFriendStatus(props.friend.id); //使用自定义hook
// ...
}
function FriendListItem(props) {
const isOnline = useFriendStatus(props.friend.id); //使用自定义hook
return (
<li style={
{
color: isOnline ? 'green' : 'black' }}>
{
props.friend.name}
</li>
);
}
const [state, dispatch] = useReducer(reducer, initialState , init);
(state, action) => newState
,一个reducer函数function init(initialCount) {
return {
count: initialCount};
}
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {
count: state.count + 1};
case 'decrement':
return {
count: state.count - 1};
case 'reset':
return init(action.payload); //重置state为initialCount
default:
throw new Error();
}
}
function Counter({
initialCount}) {
const [state, dispatch] = useReducer(reducer, initialCount, init);
return (
<>
Count: {
state.count}
<button
onClick={
() => dispatch({
type: 'reset', payload: initialCount})}>
Reset
</button>
<button onClick={
() => dispatch({
type: 'decrement'})}>-</button>
<button onClick={
() => dispatch({
type: 'increment'})}>+</button>
</>
);
}
如果 Reducer Hook 的返回值与当前 state 相同,React 将跳过子组件的渲染及副作用的执行。也就是说不会重复渲染(React 使用 Object.is 比较算法 来比较 state。)
仅当someState发生变更,才会返回新函数,否则返回之前的缓存函数。
const memoizedCallback = useCallback(
fn,
[someState]
);
useCallback(fn, deps)
相当于 useMemo(() => fn, deps)
,useCallback和useMemo区别是,前一个返回缓存的函数,后一个返回缓存的变量值。
类似于vue的computed,缓存一个函数的返回值,仅当依赖属性someState发生变更才更新这个计算值。
const computedValue = useMemo(
()=>computedValue,
[someState]
);
eg:
function WithMemo() {
const [count, setCount] = useState(1);
const [val, setValue] = useState('');
const expensive = useMemo(() => {
//昂贵的性能开销操作,进行结果缓存
console.log('compute');
let sum = 0;
for (let i = 0; i < count * 100; i++) {
sum += i;
}
return sum;
}, [count]);
return <div>
<h4>{
count}-{
expensive}</h4>
{
val}
<div>
<button onClick={
() => setCount(count + 1)}>+c1</button>
<input value={
val} onChange={
event => setValue(event.target.value)}/>
</div>
</div>;
}
//创建一个ref对象,ref.current属性初始化为initialValue值
const refContainer = useRef(initialValue);
eg:
function TextInputWithFocusButton() {
const inputEl = useRef(null); //创建ref对象
const onButtonClick = () => {
// `current` 指向已挂载到 DOM 上的文本输入元素
inputEl.current.focus(); //通过inputEl代理操作dom
};
return (
<>
<input ref={
inputEl} type="text" /> //将input的引用赋给inputEl
<button onClick={
onButtonClick}>Focus the input</button>
</>
);
}
ref透传场景时,使用ImperativeHandle Hook可以让父子组件拥有自己的ref,改变暴露给父组件的ref。
useImperativeHandle(ref, createHandle, [deps])
import React, {
useRef, useImperativeHandle } from 'react';
const FancyInput = React.forwardRef((props, ref) => {
//ref透传
const inputRef = useRef();
useImperativeHandle(ref, () => ({
//使父子组件的ref隔离开来,只能操作暴露的focus
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={
inputRef} type="text" />
});
const App = props => {
const fancyInputRef = useRef();
return (
<div>
<FancyInput ref={
fancyInputRef} />
<button
//此时使用的fancyInputRef.current是useImperativeHandle暴露的对象
onClick={
() => fancyInputRef.current.focus()}
>父组件调用子组件的 focus</button>
</div>
)
}
与effect hook一致,区别是它会在所有的 DOM 变更之后,在浏览器执行绘制之前,同步调用effect。也就是说会阻塞浏览器的绘制。推荐首先使用effect hook,性能更好。
用于在 React 开发者工具中显示自定义 hook 的标签。
useDebugValue(value)
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
// ...
// 在开发者工具中的这个 Hook 旁边显示标签
// e.g. "FriendStatus: Online"
useDebugValue(isOnline ? 'Online' : 'Offline');
return isOnline;
}
不推荐你向每个自定义 Hook 添加 debug 值。当它作为共享库的一部分时才最有价值,也就是说复用同一个hook的时候用来区分是哪个调的比较方便。