介绍:
官网上,给Hook的定义是:一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数。
本篇文章主要分为三大部分:
- 内置Hooks
- 自定义Hooks
- Hook的使用规则
内置的几个Hook
useState
入参:定义的state中变量的初始值
返回值:定义的state中变量名; 修改此变量的方法, 更新此变量时,总是替换它,而不是合并它(和this.setState( )
区别的合并不同,注意区别)
例1:
// 声明一个count的state变量,它的初始值是0,并且通过setCount方法更新count
const [count, setCount] = useState(0);
例2:
// 可在一个react组件中定义多个useState
const MultiThing = () => {
const [age, setAge] = useState(30)
const [] = useState('apple')
// error变量是Error或者null类型,并且初始值是null
const [error, setError] = useState<Error | null>(null);
}
接下来,会继续介绍其他Hook和项目中用到的自定义Hook
useEffect
官方说明:
1,可以在函数组件中执行副作用操作
2,每次运行 effect 的同时,DOM 都已经更新完毕
使用场景:useEffect会在第一次渲染之后和每次更新之后都会执行。
理解:useEffect可看成React三个生命周期钩子函数componentDidMount 、 componentDidUpdate和 componentWillUNmount 的组合
特点:
1,函数里可以访问组件的 props 和 state中的变量
2,可以在组件中多次使用useEffect
副作用操作分为需要清除和不需要清除两种:
1,对于不需要清除的,useEffect函数参数无返回值:
useEffect(() => {
document.title = title;
// 没有写返回值
}, [title]);
对于需要清除的,在useEffect函数体中,返回值为清除的函数:
执行的过程,看官网的这张图, 很好的解释了执行顺序的问题:
2,
useEffect(() => {
let timeOut = setTimeout(() => {
setDebouncesValue(param);
}, time);
// useEffect 会在组件卸载之前执行清除函数。即先执行清除函数,再执行useEffect函数体的内容。本例,先清掉上一次设的定时器timeOut,再重新设个定时器
return () => clearTimeout(timeOut);
}, [param, time]);
跳过Effect进行性能优化
1,useEffect第二个参数传入数组,作为限定。如果数组中有多个元素,即使只有一个元素发生变化,React 也会执行 effect。
useEffect(() => {
return () => {
if (!keepOnUnmount) {
document.title = oldTitle;
}
};
}, [keepOnUnmount, oldTitle]);
2,如果想某个effect仅在挂载组件时,执行一次,第二个组件,可传入空数组[]
export const useMount = (callback: () => void) => {
useEffect(() => {
callback();
// 依赖项里加callback,会造成无限循环,这和useCallback和useMemo有关
// eslint的报错信息不一定对
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
};
3, 官方文档里,useEffect函数体里用到的变量,在第二个数组参数里面要体现,但是有时候,加上了,会造成无限循环,此时,需要关掉eslint的报错信息
useReducer
官网上写是对useState的替代方案。
语法: const [state, dispatch] = useReducer(reducer, initialState, init)
init 是将initialState存入state的方法,reducer是处理state变量的过程
用法:组件中,通过调用dispatch方法,传入reducer中需要额外传入的参数; 通过state变量,访问存入的属性
官网的例子: https://codesandbox.io/s/agit...
可以看看下面例子:
// 初始化变量 initialState
const initialState = { count: 0 }
// 初始化变量修改方法recuder
const reducer = (state, action) => {
switch (action.type) {
case 'increment':
return { count: state.count + 1 }
case 'decrement':
return { count: state.count - 1 }
default:
return state
}
}
function App() {
// 初始化 useReducer。 很像 const [count, setCount] = useState(0), 赋初始值时,多传了个reducer
const [state, dispatch] = useReducer(reducer, initialState)
return <div>
count: {state.count}
{/* 修改变量,用dispatch, 传type的方式 */}
<button onClick={() => dispatch({ type: 'increment' })}>increment</button>
<button onClick={() => dispatch({ type: 'decrement' })}>decrement</button>
</div>
}
export default App
useCallback和useMemo
用法总结:
- useMemo 缓存数据
- useCallback 缓存函数
- 两者是React Hooks的常见优化策略
useMemo用法:
- 先用memo包住子组件
将需要传给子组件的对象,外面包一层useMemo
// 子组件, memo 相当于 PureComponent,对 props 进行浅层比较 const Child = memo(({ userInfo }) => { console.log('Child render...', userInfo) return <div> <p>This is Child {userInfo.name} {userInfo.age}</p> </div> }) // 父组件 function App() { const [count, setCount] = useState(0) const [name, setName] = useState('双越老师') // 用 useMemo 缓存数据,有依赖 const userInfo = useMemo(() => { return { name, age: 21 } }, [name]) return <div> <p> count is {count} <button onClick={() => setCount(count + 1)}>click</button> </p> <Child userInfo={userInfo}></Child> </div> }
useCallback用法:
- 先用memo包住子组件
将需要传给子组件的函数,外面包一层useCallback
改造以下上面的父组件,加一个onChange事件// 用 useCallback 缓存函数 const onChange = useCallback(e => { console.log(e.target.value) }, []) // 父组件内部 <Child userInfo={userInfo} onChange={onChange}></Child>
useRef
官网:useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。
要点:
- ref对象的current属性的初始值是useRef(param) 传入的参数param: const oldTitle = useRef(document.title).current;
- useRef创建的是一个JavaScript对象,而且useRef会在每次渲染时,都返回同一个ref对象
- 变更ref.current,不会引发组件重新渲染
两个应用场景:
- 获取DOM节点
获取某个ref属性的值
看例子:const btnRef = useRef(null) // 初始值 // const numRef = useRef(0) // numRef.current // 获取 numRef useEffect(() => { console.log(btnRef.current) // 获取DOM节点 }) return <div ref={btnRef}>click</div>
useContext
作用:跨组件中,值的传递
逻辑图:
用法:
- React.createContext 创建 一个Context, ThemeContext
- 在出口函数里使用 ThemeContext.provider 标签,value传入想传的值
在终端需要用到theme的地方,调用useContext,取到theme的值
以上步骤用代码实现:// 主题颜色 const themes = { light: { foreground: '#000', background: '#eee' }, dark: { foreground: '#fff', background: '#222' } } // 创建 Context const ThemeContext = React.createContext(themes.light) // 初始值 function ThemeButton() { const theme = useContext(ThemeContext) return <button style={{ background: theme.background, color: theme.foreground }}> hello world </button> } function Toolbar() { return <div> <ThemeButton></ThemeButton> </div> } function App() { return <ThemeContext.Provider value={themes.dark}> <Toolbar></Toolbar> </ThemeContext.Provider> } export default App
useImperativeHandle
自己写的例子:https://codesandbox.io/s/agit...
官网:描述内容少,写了两点。
- 在使用ref时,自定义暴露给父组件的实例值(这里的实例值的叫法,有点怪,其实是,可以调用的方法)
- useImperativeHandle 和forwardRef要一起使用
理解的过程:
useImperativeHandle中间的单词imperative是 “必要的”的意思。结合用法,这个hook的意思是:如果需要父组件调用子组件抛出的方法,进行必要的处理。
子组件用useImperativeHandle,传入自身的ref和抛出的方法。比如 实例中的setVal 和setFus,就是抛出的方法。
父组件通过绑定在子组件上的ref,调用抛出的方法。比如实例中的 setFancyFocus 和 setFancyVal 函数体
useLayoutEffect
官网:描述内容少。
说明:
- 函数签名 与 useEffect相同,不同之处是,它会在所有的 DOM 变更之后同步调用 effect
- 可以使用它来读取 DOM 布局并同步触发重渲染
后面用到了,再补充其使用场景。
useDebugValue
用法:useDebugValue(value) 在开发者工具中显示自定义hook的标签
要点:
- 用在自定义hook内部
- 格式化值的显示可能是一项开销很大的操作,除非需要检查 Hook,否则没有必要这么做
- useDebugValue可以接受格式化函数,作为第二个参数。后面用到了,再补充。
其他hook,后面会继续补充。
自定义Hook:
如果函数的名字以 “use” 开头并调用其他 Hook,我们就说这是一个自定义 Hook
使用场景:组件之间重用一些状态逻辑。
特点:自定义Hook 的每次调用都有一个完全独立的 state —— 因此你可以在单个组件中多次调用同一个自定义 Hook。
// 自定义Hook useAxios
function useAxios(url) {
const [loading, setLoading] = useState(false)
const [data, setData] = useState()
const [error, setError] = useState()
useEffect(() => {
setLoading(true)
axios.get(url)
.then(res => setDate(res))
.catch(err => setError(err))
.finally(() => setLoading(false))
}, [url])
return [loading, data, error]
}
Hook的使用规则
- Hooks的命名规范,useXXX
Hooks使用规范
- 只能用在React函数组件和自定义Hook中,其他地方不可以(其他地方,比如class组件)
- 只能用于顶层代码,不能在循环、判断 中使用Hooks,不可在Hook之前 return
// Hooks错误使用的例子 import React, { useState, useEffect } from 'react' function Teach({ couseName }) { const [studentName, setStudentName] = useState('张三') // 错误,Hooks不能放在 if判断语句中 if(couseName === ''){ useEffect(() => { localStorage.setItem('name', studentName) }) } // 错误,Hooks不能放在for循环中 for(let i = 0; i < 100; i ++){ const [teacherName, setTeacherName] = useState('双越') } if(!couseName) { return } // 错误,Hooks不能在可能被打断的逻辑后面 useEffect(() => { console.log(`${teacherName} 开始讲课,学生是 ${studentName}`) }) return <div>课程:{couseName} ,讲师: {teacherName}, 学生:{studentName}</div> } export default Teach
- eslint 插件 eslint-plugin-react-hooks 有利于Hooks使用
关于Hooks的调用顺序
无论是render还是re-render,Hooks调用顺序必须保持一致
- 对 useState 来说,render时,初始化state的值;re-render时,读取state变量的值
- 对useEffect来说,render时,初始化effect函数;re-render时,替换effect函数
如果Hooks出现在循环、判断、则无法保证顺序一致
- 不管是官方Hook还是自定义Hook 之所以能保证state变量和后面的值一一对应,是按照严格的顺序执行的方式,来保证的
- Hooks严重依赖于调用顺序
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。