React Hook
的出现,是对
react hooks
react
中无状态组件的一种升级,使得函数组件也能state 和 生命周期
React Hooks 要解决的问题是状态共享,是继 render-props(渲染属性) 和 higher-order components(HOC;高阶组件) 之后的第三种状态逻辑复用方案,不会产生 JSX 嵌套地狱问题。
1. useState
的能力,就是让我们在函数组件中使用
hooks
, 就是通过
state
来实现的
useState
UseState默认值
-
支持我们在调用的时候直接传入一个值,来指定useState
的默认值,比如这样state
,useState(0)
,useState({ a: 1 })
,useState([ 1, 2 ])
- 支持我们传入一个函数,
中的函数**只会执行一次*useState
setUseState时
- 获取上一轮的值:使用
的第二个参数时,我们想要获取上一轮该useState
的值的话,只需要在state
返回的第二个参数,也就是我们上面的例子中的useState
使用时,传入一个参数,该函数的参数就是上一轮的setCount
的值state
- 更新值:setUseState必须传入一个新的值与上次值不相等,否则将不会重新渲染(函数会继续执行)
setCount([...Array])
多个 useState 的情况
React依靠useState执行顺序来识别useState,不可以打乱其执行顺序即不能写在判断语句中
2. useEffect
可以让你在函数组件中执行副作用操作,这里提到副作用,什么是副作用呢,就是除了状态相关的逻辑,比如网络请求,监听事件,查找
Effect Hook
dom
useEffect
的第二个参数,有三种情况
- 什么都不传,组件每次 render 之后 useEffect 都会调用,相当于 componentDidMount 和 componentDidUpdate
- 传入一个空数组 [], 只会调用一次,相当于 componentDidMount 和 componentWillUnmount
- 传入一个数组,其中包括变量,只有这些变量任意一个变动时,useEffect 才会执行
3. useContext
Context
提供了一种方式,能够让数据在组件树中传递时不必一级一级的手动传递
中的
context
和
Provider
,在类组件和函数组件中都能使用,
Consumer
只能在类组件中使用,因为它是类的静态属性
contextType
import React, {createContext, useState, useContext} from 'react';
const Context = createContext(0)
function Parent () {
const [ count, setCount ] = useState(0)
return (
<div>
点击次数: { count }
<button onClick={() => { setCount(count + 1)}}>点我</button>
<Context.Provider value={count}>
<Child></Child>
</Context.Provider>
</div>
)
}
function Child () {
const count = useContext(Context);
return (
<div>{ count }</div>
)
}
4. useMemo
根据依赖的值计算出结果(类似Vue计算属性),当依赖的值未发生改变的时候,不触发状态改变
说白了React中 的
就是函数组件的
memo
,
PureComponent
同理也是
useMemo
function App () {
const [ count, setCount ] = useState(0)
const add = useMemo(() => count + 1 , [count])
return (
<div>
点击次数: { count }<br/>
次数加一: { add }<br/>
<button onClick={() => { setCount(count + 1)}}>点我</button>
</div>
)
}
也支持传入第二个参数,用法和
useMemo
类似。需要注意的是,
useEffect
会在渲染的时候执行,而不是渲染之后执行,这一点和
useMemo
有区别,所以
useEffect
不建议有 副作用相关的逻辑
useMemo
- 不传数组,每次更新都会重新计算
- 空数组,只会计算一次
- 依赖对应的值,当对应的值发生变化时,才会重新计算(可以依赖另外一个
返回的值)useMemo
const expensive = useMemo(() => {
let sum = 0;
for (let i = 0; i < count * 100; i++) {
sum += i;
}
return sum;
}, [count]);
5. useCallback
可以说是的语法糖,能用
useMemo
实现的,都可以使用
useCallback
, 在 react 中我们经常面临一个子组件渲染优化的问题,
useMemo
的第二个参数和
useCallback
一样,没有区别
useMemo
useCallback(fn, deps) 相当于 useMemo(() => fn, deps)。
主要区别是
React.useMemo
将调用
fn
函数并返回其结果,而
React.useCallback
将返回
fn
函数而不调用它。
使用场景是:有一个父组件,其中包含子组件,子组件接收一个函数作为props;通常而言,如果父组件更新了,子组件也会执行更新;但是大多数场景下,更新是没有必要的,我们可以借助useCallback来返回函数,然后把这个函数作为props传递给子组件;这样,子组件就能避免不必要的更新。
function Parent() {
const [count, setCount] = useState(1)
const [val, setVal] = useState('')
const callback = useCallback(() => {return count}, [count])
return (<div>
<h4>{count}</h4>
<Child callback={callback}/>
<div>
<button onClick={() => setCount(count + 1)}>+</button>
<input value={val} onChange={event => setVal(event.target.value)}/>
</div>
</div>)
}
function Child({ callback }) {
const [count, setCount] = useState(() => callback())
useEffect(() => {setCount(callback())}, [callback])
return <div>{count}</div>
}
6. useRef
的作用,总共有两种用法
useRef
- 获取子组件的实例(只有类组件可用)
- 在函数组件中的一个全局变量,不会因为重复
重复申明, 类似于类组件的
render
this.xxx
对于第2点:
有些情况下,我们需要保证函数组件每次 render 之后,某些变量不会被重复申明,比如说 Dom 节点,定时器的 id 等等,在类组件中,我们完全可以通过给类添加一个自定义属性来保留,比如说 this.xxx, 但是函数组件没有 this,自然无法通过这种方法使用,有的朋友说,我可以使用
useState 来保留变量的值,但是 useState 会触发组件 render,在这里完全是不需要的,我们就需要使用 useRef 来实现了,具体看下面例子
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 = () => {clearInterval(timer.current)}
const onClick = () => {clearInterval(timer2)}
return (
<div>
点击次数: { count }
<button onClick={onClick}>普通</button>
<button onClick={onClickRef}>useRef</button>
</div>
)
}
当我们们使用普通的按钮去暂停定时器时发现定时器无法清除,因为 App 组件每次 render,都会重新申明一次 timer2, 定时器的 id 在第二次 render 时,就丢失了,所以无法清除定时器,针对这种情况,就需要使用到 useRef,来为我们保留定时器 id,类似于 this.xxx,这就是 useRef 的另外一种用法
7. useReducer
是什么呢,它其实就是类似
useReducer
中的功能,相较于
redux
,它更适合一些逻辑较复杂且包含多个子值,或者下一个
useState
依赖于之前的
state
等等的特定场景,
state
总共有三个参数
useReducer
- 一个 reducer,就是一个函数类似 (state, action) => newState 的函数,传入 上一个 state 和本次的 action
- 初始 state,也就是默认值,是比较简单的方法
- 惰性初始化,这么做可以将用于计算 state 的逻辑提取到 reducer 外部,这也为将来对重置 state 的 action 做处理提供了便利
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function App() {
const [state, dispatch] = useReducer(reducer, {count: 0});
return (
<>
点击次数: {state.count}
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</>
);
}
8. useEffect和useLayoutEffect的区别
useEffect是在渲染完成后执行,如果在渲染后执行DOM操作就可能出现元素的闪烁、瞬移,useLayoutEffect里面的callback函数会在DOM更新完成后立即执行,但是会在浏览器进行任何绘制之前运行完成,阻塞了浏览器的绘制.
区别就在执行的时间不同:
DOM更新 ⇒ 渲染 ⇒ useEffect
DOM更新 ⇒ useLayoutEffect ⇒ 渲染
9. 自定义Hook
关于自定义hook使用:
当组件的整个逻辑可以抽离时候,适当的封装起来,便于引用使用。打个比方,用React Hook 想做一个输入框的UI组件,我们把逻辑抽离,对于UI组件的样式就可以随便写,有几种样式就能生成几种不同的输入框组件,只需要引入自定义hook实现逻辑复用。