React Hooks 是 React v16.8 版本引入了全新的 API,其设计目的就是加强版函数组件,完全不使用"类",就能写出一个全功能的组件。
一、作用
Hook 这个单词的意思是"钩子"。
React Hooks 的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来。 React Hooks 就是那些钩子。
你需要什么功能,就使用什么钩子。React 默认提供了一些常用钩子,你也可以封装自己的钩子。
所有的钩子都是为函数引入外部功能,所以 React 约定,钩子一律使用 use
前缀命名,便于识别。你要使用 xxx 功能,钩子就命名为 usexxx。
下面介绍 React 默认提供的钩子。
* useState()
* useEffect()
* useContext()
* useReducer()
* useMemo()
* useRef()
二、useState()
useState()
用于为函数组件引入状态(state)。纯函数只有属性不能有状态,所以把状态放在钩子里面。
import React, {useState} from 'react';
const [n, setN] = useState(0);
const [user, setUser] = useState({name: 'Tom', age: '18'}];
useState() 这个函数接受状态的初始值,作为参数。该函数返回一个数组,数组的第一个成员是一个变量(上例是 n / user),指向状态的当前值。第二个成员是一个函数,用来更新状态,约定是 set 前缀加上状态的变量名(上例是 setN / setUser)。
- 更新状态方法一:setxxx() 括号里面直接修改
setN(n + 1)
setUser({
...user,
name: 'Jerry'
})
需要注意的是 setxxx 不能局部更新:
setUser(name: 'Jerry')
// 这样做的结果是对象最后只有一个 name 属性了
- 更新状态方法二:setxxx() 括号里面写函数
setN( i => i + 1)
因为 setxxx 是异步更新,n 的值不会马上改变,如果在 setN 的作用域内紧接着对 n 进行下一步操作的话,最终只会执行最后一步操作,所以如果需要对 n 进行多步操作的话就使用传函数的方法。
三、useEffect()
useEffect()
接受两个参数。第一个参数是一个函数,异步操作的代码放在里面。第二个参数是一个数组,用于给出 Effect
的依赖项,只要这个数组发生变化,useEffect()
就会执行。
- 用法
- 第二个参数传
[]
空数组,作为 componentDidMount 使用,组件第一次渲染的时候执行
import React, {useEffect} from 'react';
useEffect(() => {
// Async Action
}, [])
- 第二个参数传变量,当变量变化后组件渲染执行
- 第二个参数不传,当任何变量变化后组件每一次渲染执行
上面两种都是作为 componentDidUpdate 使用,但是组件第一次渲染的时候也会执行,如果不需要第一次执行可以设置一个判断语句:
useEffect(() => {
if (n !== 初始值) {
// actions
}
}, [n])
- 还可以作为 componentWillUnmount 使用,在操作完成后 return 就可以了
useEffect(() => {
let timer = setInterval(() => {console.log('hi')}, 1000)
return () => {clearInterval(timer)
}, [])
- 特点
允许多个 useEffect 同时存在,如果存在多个,会按照出现顺序依次执行。
四、useContext()
如果需要在组件之间共享数据,可以使用useContext()
。
- 使用 name = createContext(defaultValue) 创建一个共享数据
- 使用
圈定其作用范围,一般会结合 useState 来操作数据
const C = createContext(null)
function App() {
const [n, setN] = useState(0)
return (
)
}
- 范围内的所有组件及子孙组件都可以使用 useContext(name) 来使用该数据
function Grandson() {
const {n, setN} = useConText(C)
const click = () => { setN(i => i + 1) }
return (
{n}
)
}
- 需要注意的是 react 可以有 Context 的嵌套,如果存在这种情况,而且嵌套的 Context 还提供了相同的方法,子组件只会匹配最近的 provider
五、useReducer()
可以看成是 useState 的复杂版,优点是可以把多个重复使用的方法汇聚起来,使代码更简洁。
- useState 提供一个初始值,返回一个变量和一个能够操作该变量的函数,具体操作自己在函数里面定义执行;
- useReducer 提供一个初始值和一个函数,函数里面给对数据的不同操作做标记,返回一个变量和一个能够对应标记提取操作的函数。
const [state, dispatch] = useReducer(reducer, initialState)
用法:
- 创建初始值
const initialState = {
n: 0,
m: 2
}
- 创建不同操作标记
const reducer = (state, action) => {
switch(action.type) {
case('add'):
return {...state, n: state.n+1};
case('subtract'):
return {...state, m: state.m-action.number);
default: return state;
}
}
- 使用
function App() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
n: {state.n}
m: {state.m}
);
}
六、useMemo()
要讲 useMemo 首先要了解 memo,memo类似于PureCompoent 作用是优化组件性能,防止组件触发重渲染
function App() {
const [n, setN] = useState(0);
const [m, setM] = useState(0);
const click = () => {
setN(n + 1)
}
return (
);
function Child(props) {
console.log('假如有大量代码')
return child: {props.data}
}
const Child2 = React.memo(Child)
}
Child 经过 memo 后当点击按钮的时候就不会重新渲染了,但是当 Child 有函数逻辑的时候:
const clickChild = () => {}
return (
)
即使 memo 了它依然还是会重新渲染,因为当 n 变化的时候 APP 会重新渲染,clickChild = () => {} 重新运行,得到另外一个空函数,地址不同组件内容改变,所以 Child 还是会重新渲染,这时候就可以使用 useMemo 来优化了。
const clickChild = useMemo(() => {
return () => {}
}, [])
把 Child 的所有函数逻辑全部用 useMemo 返回出来就好了,后面的数组和 useEffect 的用法一样,忽略就每次返回新函数,空数组第一次返回,把和函数逻辑相关的变量放进去就是每次变量变化的时候返回新函数。
因为 useMemo 本身是个函数又返回一个函数,写起来就有点多,可以用语法糖 useCallback 即:
const clickChild = useCallback(() => {}, [])
对比可以看出,memo是针对 一个组件的渲染是否重复执行,useMemo是针对 一段函数逻辑是否重复执行。
七、useRef()
因为在 React 的函数组件中,一个全局变量会因为组件重新渲染而重复声明,就导致每次都是一个新的变量地址,就没法对上一次的变量进行操作了,如果想要这个变量在始终可操作可以用 useRef 来实现:
useRef()
返回一个可变的 ref
对象,其 .current
属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。
function App () {
const [ count, setCount ] = useState(0)
const timer = useRef(null)
let timer2
useEffect(() => {
let id = setInterval(() => {
setCount(count => count + 1)
}, 500)
timer.current = id
timer2 = id
return () => {
clearInterval(timer.current)
}
}, [])
const onClickRef = useCallback(() => {
clearInterval(timer.current)
}, [])
const onClick = useCallback(() => {
clearInterval(timer2)
}, [])
return (
点击次数: { count }
)
}
八、自定义 Hook
React Hooks 最厉害的地方就是它可以自定义 Hook,它可以把全局数据和方法放在指定的地方统一管理,组件用到了可以直接引入调用,自定义 Hook 需要遵守一个规则就是命名必须以 use
开头。下面举个例子,因为 useEffect 总是在组件第一次渲染就会运行,我们可以自定义一个第一次不运行,当依赖变化的时候再运行:
import {useEffect, useRef} from 'react'
export const useUpdate = (fn, deps) => {
const count = useRef(0)
useEffect(() => {
count += 1
})
useEffect(() => {
if (count > 1) {
fn()
}
}, [fn, deps])
}