hook简介
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。践行函数式编程的方式,在函数组件中使用,获得class编写的特性。
useState和useReducer
useState
-
useState
给一个state赋初始值,并且返回一个数组,一个用来读取state的值,一个用来设置state的值。 -
useState
声明的state在发生变化的时候,会触发组件的更新。 -
setState
更新state是异步更新策略,参数有两种,一种要更新的值,第二种是一个更新的函数,返回要更新的值。 -
setState
不会进行合并更新,只会全量更新,所以参数或回调函数值应该要更新的值。
const [state, setState] = useState(10);
// 赋值修改
setState(20) // 此方法就是将state修改为20
// 函数修改
setState(current => {
// current 是当前state的值
return current + 20; // 修改state的值为40
// return state + 20; // 这样也是可以的
})
useReducer
useState
的替代方案。它接收一个形如 (state, action) => newState
的 reducer,并返回当前的 state 以及与其配套的 dispatch
方法。(如果你熟悉 Redux 的话,就已经知道它如何工作了。)
用法简介
const [state, dispatch] = useReducer(reducer, initialState, init);
-
reducer
是一个函数,该函数接受两个参数,一个是state
一个是action
, 是修改state值得函数。 -
initialState
初始值 -
init
一个函数,用于初始化时调用的函数,返回的就是初始化的state
。 -
useReducer
返回一个状态state
和dispatch
,state是返回状态的值,dispatch是可以发布事件来更新state的函数,修改state值得韩式就是reducer。
指定初始值
function counter = () => {
// 声明count,返回一个count 和 dispatch
const [count, dispatch] = useReducer((state, action) => {
// state 就是当前count的值
// action 就是dispatch函数的参数,可以为任何类型的数据
if (action === 'add') {
return state + 1;
} else {
return state - 1;
}
}, 0) // 默认初始值为0
return (
<div>
<h1>{count}</h1>
{* 调用dispatch来更新count *}
<button onClick={dispatch('sub')}> -1 </button>
<button onClick={dispatch('sub')}> +1 </button>
</div>
)
}
惰性初始化
如果state初始化时需要有重置的操作或者state
初始化的值十分复杂,都可以使用useReducer
的第三个参数init
const initState = 10;
// 重置时会走该函数
const init = initState => {
return initState;
}
function counter = () => {
// 声明count,返回一个count 和 dispatch
const [count, dispatch] = useReducer((state, action) => {
// state 就是当前count的值
// action 就是dispatch函数的参数,可以为任何类型的数据
if (action === 'add') {
return state + 1;
} else if(action === 'sub') {
return state - 1;
} else if (action === 'reset') {
return init(initState);
}
}, initState, init)
return (
<div>
<h1>{count}</h1>
{* 调用dispatch来更新count *}
<button onClick={dispatch('sub')}> -1 </button>
<button onClick={dispatch('sub')}> +1 </button>
<button onClick={dispatch('reset')}> 重置 </button>
</div>
)
}
注意
- 当
useReducer
返回的state和当前的一样时,是不会重新渲染该组件的,因为react使用Object.js
来比较state。 - 对于state有比较复杂的赋值操作时建议使用
useReducer
来声明和处理。
useEffect 和 useLayoutEffect
useEffect
-
useEffect
可以执行副作用的操作 -
useEffect
有两个参数,一个回调函数,一个数组类型变量(这里面的值必须是依赖项,就是使用useState等声明的变量,可以触发组件更新的变量) -
useEffect
会在每次render的时候都执行,可以通过参数控制只在初始化时执行、某些数据变化时执行 - 相当于class组件的声明周期
componentDidMount
(组件渲染后执行)和componentDidUpdate
(组件更新后执行) -
useEffect
是一个回调函数,如果返回一个函数时,则会在componentWillUnmount
(组件销毁)时执行
只在初始化时执行
在第二个参数中传递[]
空数组,则只在初始化时调用
useEffect(() => {
// 初始化时获取list数据
getList();
}, [])
在初始化和render更新时执行
第二个参数不穿时,会在初始化和更新时执行
useEffect(() => {
getList();
})
在某些数据变化时执行
// 在count数据变化的时候执行
useEffect(() => {
getList();
}, [count]) // 数组中可以放许多的变量,在这些数据变化时都会执行
useLayoutEffect
- 当需要处理dom时,使用
useEffect
会导致闪屏问题 -
useLayoutEffect
会在DOM更新完成后立即执行,在浏览器进行回执之前运行 - 回阻塞浏览器的绘制
useContext
介绍
- 接受一个context(React.createContext的返回值)对象
-
useContext
帮助我们跨越组件层级直接传递变量,实现共享,解决了组件之间传值的问题。 - context对象提供
provider
属性将数据可以通过垮组件访问,通过value将值传给子组件 -
useContext
获取的数据是跟随数据源里的数据的变化而变化的。
const value = useContext(MyContext);
使用
// 父组件 parent.js
import React, { useState, createContext } from 'react'
import Children from './children.js'
const Parent = () => {
const [count, setCount] = useState(10);
const CountContext = createContext();
return (
<>
<CountContext.Provider value={{count}}>
<Children countContext={CountContext}/>
</CountContext.Provider>
<h1>{ count }</h1>
<button onClick={() => setCount(count + 1)}>
加1
</button>
</>
)
}
// 子组件 children.js
import React, { useContext } from 'react'
const Children = props => {
// 这里的countContext是从组件传递过来的
// 也可以把createCount单独放到模块里面进行引用
// 引用模式应该比props好,可以适应组件嵌套的问题
const { countContext } = props;
// 这里的数据是随着父组件中的count数据变化而变化的
const countData = useContext(countContext);
return (
<h1>{ countData.count }</h1>
)
}
useRef 与 useImperativeHandle
useRef
const refContainer = useRef(initialValue);
-
useRef
返回一个可变的ref对象,其.current
属性就是被初始化传入的参数。 -
useRef
中的值发生变化不会触发组件的更新 -
useRef
可以用来保存任何可变值。 - 绑定到原生html上面可以获取该标签的方法,绑定到自定义组件上,则不要通过
useImperativeHandle
将部分方法和属性暴露出来。 -
useImperativeHandle
可以在使用ref的时候自定义暴露给父组件的实例值和方法,需要和forwardRef
一起使用
useImperativeHandle
useImperativeHandle(ref, createHandle, [deps])
- ref:定义 current 对象的 ref createHandle:一个函数,返回值是一个对象,即这个 ref 的 current对象
- [deps]:即依赖列表,当监听的依赖发生变,useImperativeHandle 才会重新将子组件的实例属性输出到父组件
- ref 的 current 属性上,如果为空数组,则不会重新输出。
实例
// 父组件 parent.js
import React, {useRef} from 'react'
import Children from './children'
const Parent = () => {
const childRef = useRef();
return (
<>
<Children ref={childRef} />
<button onClick={() => childRef.current.addCount()} >+1</button>
</>
)
}
// children.js
import React, {useState, useImperativeHandle, forwardRef} from 'react'
// 第二个参数才是ref,第一个是props
const Children = (props, ref) => {
const [count, setCount] = useState(1)
useImperativeHandle(ref, () => {
addCount: setCount
})
return (
<h1>{count}</h1>
)
}
// 需要使用forwardRef进行一次转发
export default forwardRef(Children);
useCallback和useMemo
- 都是用于数据缓存的方法
- 都可以根据依赖项进行刷新
- 主要是用在不需要随着组件更新而更新的情况时,用于优化部分复杂函数更新问题
-
useCallback
返回函数,并不调用他们 -
useMemo
调用函数,返回执行的结果
用法
// useCallback 返回一个函数
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
// useMemo 执行函数,返回执行的结果
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
例子
// parent.js
import React, {useState, useCallback, useMemo} from 'react'
import ChildrenComponent from './Children.js'
const Parent = () => {
const [count, setCount] = useState(10)
const [num, setNum] = useState(10)
// 只有count变化的时候countName才会更新,可以作为优化部分
const countName = useMemo(() => `年龄:${count}`, [count]);
// 这样,只有在num更新的时候,children组件才会更新
const setDataNum = useCallback(() => setNum(num + 10), [num]);
return (
<div>
<h1>
{countName}
</h1>
<button onClick={() => setCount(count + 1)}>+1</button>
<H1>NUM {num}</H1>
<ChildrenComponent setData={setDataNum}/>
</div>
)
}
// children.js
import React from 'react';
const Children = ({setData}) => {
return (
<button onClick={() => setData()}>加10</button>
)
}
export React.memo(Children)