React Hook
常用的一些hook,以及hook的源码解析
学习中的一些拙劣看法,如有错误,欢迎指正~~
useState
本质上也利用了useReducer
简单使用
const [state, setState] = useState({
age: 18,
name: 'daniel',
})
setState((s) => ({
...s,
age: 20
}))
<div>{state.name}: {state.age}</div>
useState源码
源码解析
自定义简单实现
// 本质上useState也是用useReducer实现的
let hooksState = []; // 用来保存hooks
let hooksIndex = 0; // 用来指定下标
function useState(initialState) {
// 初始值可能是一个函数
let initState = typeof initialState === 'function' ? initialState() : initialState
// 把老的值取出来,如果没有,就用 新的值
hooksState[hooksIndex] = hooksState[hooksIndex] || initState
// 定义一个内部变量保存当前的值
const currentIndex = hooksIndex;
const setState = (newState) => {
if (typeof newState === 'function') {
newState = newState(hooksState[currentIndex])
}
hooksState[currentIndex] = newState
// 更新
render()
}
// 让hooksIndex 加1,用于下一个hook
return [hooksState[hooksIndex++], setState];
}
useEffect
副作用,在浏览器渲染完成之后执行,React 更新 DOM 之后运行一些额外的代码
简单使用
// 如果没有依赖项,会在每一次渲染后都会执行
useEffect(() => { console.log('每一次都执行') })
// 只会首次渲染执行一次
useEffect(() => {
console.log('初始化渲染执行一次')
},[])
// 在依赖项改变之后才执行, 包括首次渲染也会执行
useEffect(() => {
console.log('num 改变就执行',‘初始化也会执行’)
// 清楚一些副作用,返回一个函数
return () => {
// 页面卸载的时候执行,相当于componentWillUnmount 如果有依赖项,依赖项改变就执行
console.log('清楚effect')
}
},[num])
useEffect源码
源码解析
自定义简单实现
function useEffect(callback, deps) {
if (hooksState[hooksIndex]) {
const [ lastDestroyFunction, lastDeps ] = hooksState[hooksIndex]
// 对比依赖项有没有变化
cosnt allDepsChange = deps && deps.every((item, index) => item === lastDeps[index])
if (allDepsChange) {
hooksIndex++
}else {
lastDestroyFunction && lastDestroyFunction()
setTimeout(() => {
const destroyFunction = callback()
hooksState[hooksIndex++] = [destroyFunction, deps]
})
}
} else { // 第一次渲染
setTimeout(() => {
const destroyFunction = callback()
hooksState[hooksIndex++] = [destroyFunction, deps]
})
}
}
useRef
useRef 返回一个可变的对象,其current 属性被初始化为传入的参数,它可以很方便地保存任何可变值
当我们使用useState的时候,setState之后,并不能立刻拿到最新的值,因为是一个闭包,拿到的是上一次的值,只有在页面再次渲染之后才能拿到最新值,这个时候,可以利用useRef 的current 将值保存,就能立刻拿到最新的值了
demo
const Counter = () => {
const [num, setNum] = useState(0);
const numRef = useRef(0)
const changeNum = () => {
setNum(num + 1)
numRef.current = numRef.current + 1;
console.log(num, '----->num') // 第一点击是 0
console.log(numRef.current, '----->numRef.current') // 第一次点击是 1
}
return (
<>
<h2>num: {num}</h2>
<h2>Ref {numRef.current}</h2>
<button onClick={changeNum}>点击num</button>
</>
)
}
useRef源码
源码解析
自定义简单实现
function useRef(initialState) {
hooksState[hooksIndex] = hooksState[hooksIndex] || { current: initialState }
return hooksState[hooksIndex++]
}
useContext
当有的值需要跨组件传值的时候,比如说父组件给子组件的子组件传值,一种方法就是利用props依次往下传,还有就是利用context,创建一个context,然后利用provider提供值,利用consumer消费值,也可以利用useContext 传入context 实例获取到context上的值
看一个简单使用
创建一个context 新增一个ctx.jsx文件
import React from 'react'
export const contextTheme = React.createContext(null)
父组件 给孙组件传 color 和 setColor
// 父组件
import ChildTest from './ChildTest'
import { contextTheme } from './ctx.jsx'
const DrawingBoard: React.FC = () => {
const [ color, setColor ] = useState('red')
return (
<div>
<contextTheme.Provider value={{ color, setColor }}> // 利用context传数据
<ChildTest ref={childRef} />
</contextTheme.Provider>
</div>
</div>
)
}
export default DrawingBoard
// 子组件
import ChildChildTest from './ChildChildTest'
const ChildTest: React.FC<Props> = (props) => {
return (
<div>
<ChildChildTest />
</div>
)
}
子组件的子组件
方式一: 利用 contextTheme.Consumer 来消费
import { contextTheme } from './ctx' // 引入contextTheme
interface Props {}
const ChildChildTest: React.FC<Props> = (props) => {
const changColor = (setColor) => {
setColor('blue')
}
return (
<div>
<contextTheme.Consumer> // 消费父组件的 color 和 setColor
{
({color, setColor}) => (
<>
<span> 孙子组件 {color}</span>
<Button onClick={() => changColor(setColor)} style={{ background: `${color}` }} >孙组件按钮</Button>
</>
)
}
</contextTheme.Consumer>
</div>
)
}
export default ChildChildTest;
孙组件 方式二 (写法比方式一简单灵活)
利用useContext 来消费
import React, from 'react'
import { contextTheme } from './ctx'
interface Props {}
const ChildChildTest: React.FC<Props> = (props) => {
const values = useContext(contextTheme)
const { color, setColor } = values;
const changColor = () => {
setColor('blue')
}
return (
<div>
<span> 孙子组件 {color}</span>
<Button onClick={changColor} style={{ background: `${color}` }} >孙组件按钮</Button>
</div>
)
}
export default ChildChildTest;
useContext源码
源码解析
自定义简单实现
function useContext(context) {
return context._currentValue
}
createContext源码
源码解析
自定义简单实现
function createContext(initialValue = {}) {
const context = { Consumer, Provider }
function Provider(props) {
context._currentValue = context._currentValue || initialValue;
Object.assign(context._currentValue, props.value)
return props.children
}
function Consumer(props) {
return props.children(context._currentValue)
}
return context
}
useCallback
当父组件渲染的时候,里面的子组件也会被渲染一遍,为了避免这些不必要的渲染,我们可以使用React.memo 来包裹子组件,当子组件的props改变的时候才会进行渲染,注意: react.memo是进行浅比较,所以引用类型的props无法使用它来优化,来看一个小例子
// 父组件
const Counter = () => {
const [num, setNum] = useState(0);
console.log('父组件render')
const changeNum = () => {
setNum(num + 1)
}
return (
<>
<h2>父组件</h2>
<h2>{num}</h2>
<button onClick={changeNum}>点击num</button> // 父组件点击会触发子组件渲染
<Count num={1} />
<Count num={{}} /> // 这个会渲染两次
</>
)
}
// 子组件
import React from 'react';
const Count = (props) => {
console.log('子组件render') // 当props.num 是引用类型的时候,React.memo无效, 还是会打印两次
return (
<div>
<div style={{ background: 'red' }}>{`父组件给子组件传的值: ${props.num}`}</div>
</div>
);
}
export default React.memo(Count);
引出useCallBack 返回一个 memoized 回调函数。该回调函数仅在某个依赖项改变时才会更新
useCallBack(fn, [dep]) 相当于 useMemo(() => fn, [dep])
看下面的例子
// 子组件
const Count = (props) => {
const { testChange } = props;
console.log('子组件render')
return (
<div>
<div style={{ background: 'red' }}>{`父组件给子组件传的值: ${props.num}`}</div>
<button onClick={() => testChange('啦啦啦')}>子组件传值给父组件</button>
</div>
);
}
export default Count;
// 父组件
const Counter = () => {
const [num, setNum] = useState(0);
const [age, setAge] = useState(0);
console.log('父组件render')
const changeNum = () => {
setNum(num + 1)
}
const func = (data) => {
console.log('子组件调用父组件给他的函数进行传值',data)
}
return (
<>
<h2>父组件</h2>
<h2>num: {num}</h2>
<h2>age: {age}</h2>
<button onClick={changeNum}>点击num</button>
<button onClick={() => setAge(age +1)}>点击age</button>
<Count num={num} testChange={func} /> // 正常情况下,不管更新num还是age子组件都会渲染
// 使用useCallBack 只有num改变的时候才会渲染子组件,改变age并不会重新渲染,即使有传给子组件的函数也不会更新, 这里也可以抽离到最上面
{
useCallback(<Count num={num} testChange={func} />, [num] )
}
// 如果利用useMemo 就相当于
{
useMemo(() => (<Count num={num} testChange={func} />), [num] )
}
</>
)
}
1.如果props 只是传一个基本类型的值,可以利用react.memo 优化。
2.如果传了基本类型的值和一个函数,也可以利用useCallback 缓存函数,然后加上React.memo 进行优化, 或者是直接将组件利用useCallback缓存。就不需要react.memo了
3.如果是引用类型的props和函数,可以直接利用useCallback 或者 useMemo 缓存。或者 useCallBack 缓存函数,useMemo 缓存值,看情况使用
useCallback源码
源码解析
自定义简单实现
useMemo
返回一个 memoized 值。
把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。 具体看useCallback 两者相同
{
useCallback(<Count num={num} testChange={func} />, [num] )
}
// 如果利用useMemo 就相当于
{
useMemo(() => (<Count num={num} testChange={func} />), [num] )
}
useCallback是根据依赖项(deps)缓存第一个参数callback
useMemo是根据依赖项(deps)缓存第一个参数callback执行后的值。
useCallback会重新返回一个函数体,而useMemo返回的是一个缓存计算数据的值
当依赖项变化时,usecallback会重新创建一个函数体,而useMemo不会。
useLayoutEffect
会在浏览器渲染完成之前执行,阻塞浏览器渲染,可以解决页面闪烁问题,也可能造成页面卡顿,使用方法与useEffect 一样
官网介绍: 其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。
尽可能使用标准的 useEffect 以避免阻塞视觉更新。
useLayoutEffect源码
源码解析
自定义简单实现
function useLayoutEffect(callback, deps) {
if (hooksState[hooksIndex]) {
const [ lastDestroyFunction, lastDeps ] = hooksState[hooksIndex]
// 对比依赖项有没有变化
cosnt allDepsChange = deps && deps.every((item, index) => item === lastDeps[index])
if (allDepsChange) {
hooksIndex++
}else {
lastDestroyFunction && lastDestroyFunction()
queueMicrotask(() => {
const destroyFunction = callback()
hooksState[hooksIndex++] = [destroyFunction, deps]
})
}
} else { // 第一次渲染
queueMicrotask(() => {
const destroyFunction = callback()
hooksState[hooksIndex++] = [destroyFunction, deps]
})
}
}
useReducer
利用useReducer来管理复杂的状态
在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为你可以向子组件传递 dispatch 而不是回调函数 。
demo
import React, { useReducer } from 'react'
const Test = (props) => {
// useReducer的初始值
const store = {
count: 1
}
const reducer = (state:any, action:any) => {
console.log(state, '------state----->') // 1
switch (action.type) {
case 'increment':
return { count: state.count + 1 }
case 'decrement':
console.log(action.count, '---->action.count') // 4
return {...state, count: action.count};
default:
throw new Error();
}
}
const [state, dispatch] = useReducer(reducer, store);
const testIncream = () => {
dispatch({type: 'increment'})
}
const testDecream = () => {
dispatch({count: 4, type: 'decrement'}) // 可以给dispatch传递不同的参数
}
return (
<div>
<div>testReducer {state.count}</div>
<button onClick={testIncream}>-</button>
<button onClick={testDecream}>+</button>
</div>
)
}
export default Test
useReducer源码
源码解析
自定义简单实现
function useReducer(reducer, initialState) {
hooksState[hooksIndex] = hooksState[hooksIndex] || (typeof initialState === 'function' ? initialState() : initialState)
let currentIndex = hooksIndex
function dispatch(action) {
let lastState = hooksState[currentIndex]
let nextState;
if (typeof action === 'function') {
nextState = action(lastState)
}
if (reducer) {
nextState = reducer(nextState, action)
}
// hooksState[currentIndex] = reducer ? reducer(hooksState[currentIndex], action) : action;
hooksState[currentIndex] = nextState
scheduleUpdate()
}
return [hooksState[hooksIndex++], dispatch]
}
useImperativeHandle
与forwardRef配合使用转发ref,(props不能传ref) 父组件调用子组件的ref实例方法,
父组件
const childRef = useRef<fun | null>(null)
console.log(childRef, 'childRef')
const parentAddNum = () => {
console.log('点击父组件')
childRef.current!.addChildnum()
}
<ChildTest ref={childRef} />
<Button onClick={parentAddNum}> 父组件 </Button>
子组件将addChildnum 方法传给父组件让父组件调用
子组件代码
import React, { useState, forwardRef, useImperativeHandle } from 'react'
import { Button } from 'antd'
interface Props {}
const ChildTest: React.FC<Props> = forwardRef((props, ref) => {
const [ num, setNum ] = useState<number>(0)
const [ age, setAge ] = useState<number>(0)
const addNum = () => {
setNum(num + 1)
}
useImperativeHandle(ref, () => ({
addChildnum: () => setNum(num + 1)
}))
return (
<div>
<span> 子组件的数字: {num}</span>
<Button onClick={addNum}>子组件的按钮</Button>
<Button onClick={() => setAge(age+1)}>子组件age的按钮</Button>
</div>
)
}
)
export default ChildTest;
useImperativeHandle源码
源码解析
自定义简单实现
function useImperativeHandle(ref, handle) {
ref.current = handle()
}
forwardRef 源码
自定义简单实现
function forwardRef(FunctionComponent) {
return class extends Component {
render() {
return FuncionComponent(this.props, this.ref) // this是类的实例
}
}
}
自定义Hook
抽离一些重复的逻辑片段
具体可以看另一篇文章,利用自定义hook封装一些代码片段
看到这里了,点个赞吧👍 ~~