React Hooks

本文深入探讨了React Hooks,包括useState、useCallback、useMemo、useReducer、useContext、useEffect和useLayoutEffect+useRef的用法和场景。useState提供状态管理和更新函数,useCallback和useMemo用于优化函数和值的计算,useReducer适用于复杂状态管理,useContext用于获取和订阅context值,useEffect处理副作用,useLayoutEffect则在DOM更新后同步执行,forwardRef和useImperativeHandle则涉及组件间引用的处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Hook可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性

useState

useState 就是一个 Hook
通过在函数组件里调用它来给组件添加一些内部 state,React 会在重复渲染时保留这个 state
useState 会返回一对值:当前状态和一个让你更新它的函数,你可以在事件处理函数中或其他一些地方调用这个函数。它类似 class 组件的 this.setState,但是它不会把新的 state 和旧的 state 进行合并
useState 唯一的参数就是初始 state
返回一个 state,以及更新 state 的函数
在初始渲染期间,返回的状态 (state) 与传入的第一个参数 (initialState) 值相同
setState 函数用于更新 state。它接收一个新的 state 值并将组件的一次重新渲染加入队列

let hookState = []; // 这是全局变量,用来记录hook的值
let hookIndex = 0; // 存放当前hook的索引值
let scheduleUpdate;
function render(vdom, container) {
    mount(vdom,container);
    // react里不管哪里触发的更新,真正的调度都是从根节点开始的
    scheduleUpdate = ()=>{
      hookIndex = 0; // 索引值重置为0
      // 从根节点执行完整的dom-diff,进行组件更新
      compareTwoVdom(container,vdom,vdom);
    }
}
function useState(initailState) {
	hookState[hookIndex] = || hookStates[hookIndex]||initialState;
	let currentIndex = hookIndex;
	function setState(newState){
      if(typeof newState === 'function') newState=newState(hookStates[currentIndex]);
      hookStates[currentIndex]=newState;
      scheduleUpdate(); // 状态变化后,要执行调度更新任务
    }
    return [hookStates[hookIndex],setState];
}

useCallbackuseMemo

把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新
把创建函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算

let  Child = ({data,handleClick})=>{
  console.log('Child render');
  return (
     <button onClick={handleClick}>{data.number}</button>
  )
}
Child = React.memo(Child);

function App(){
  console.log('App render');
  const[name,setName]=React.useState('zhufeng');
  const[number,setNumber]=React.useState(0);
  let data = React.useMemo(()=>({number}),[number]);
  let handleClick = React.useCallback(()=> setNumber(number1),[number]);
  return (
    <div>
      <input type="text" value={name} onChange={event=>setName(event.target.value)}/>
      <Child data={data} handleClick={handleClick}/>
    </div>
  )
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);
/**   
 * factory 可以用来创建对象的工厂方法
 * deps 依赖数组
*/
export  function useMemo(factory,deps){
    if(hookStates[hookIndex]){
      let [lastMemo,lastDeps] = hookStates[hookIndex];
      let same = deps.every((item,index)=>item === lastDeps[index]);
      if(same){
        hookIndex;
        return lastMemo;
      }else{
        let newMemo = factory();
        hookStates[hookIndex]=[newMemo,deps];
        return newMemo;
      }
    }else{
      let newMemo = factory();
      hookStates[hookIndex]=[newMemo,deps];
      return newMemo;
    }
}
export function useCallback(callback,deps){
    if(hookStates[hookIndex]){
      let [lastCallback,lastDeps] = hookStates[hookIndex];
      let same = deps.every((item,index)=>item === lastDeps[index]);
      if(same){
        hookIndex;
        return lastCallback;
      }else{
        hookStates[hookIndex]=[callback,deps];
        return callback;
      }
    }else{
      hookStates[hookIndex]=[callback,deps];
      return callback;
    }
}

useReducer

useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法
在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等

function reducer(state= {number:0}, action){
	switch(action.type){
		case 'ADD':
			return {number:state.numberr  1}
		case 'MINUS':
			return {number:state.numberr - 1}
		default:
			return state;
	}
}
function Counter() {
	const [state, dispatch] = React.useReducer(reducer,{number:0})
	return (
        <div>
          Count: {state.number}
          <button onClick={() => dispatch({type: 'ADD'})}></button>
          <button onClick={() => dispatch({type: 'MINUS'})}>-</button>
        </div>
    )
}

useState就是useReducer的一个语法糖

useContext

接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值
当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定
当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext provider 的 context value 值
useContext(MyContext) 相当于 class 组件中的 static contextType = MyContext 或者 <MyContext.Consumer>
useContext(MyContext) 只是让你能够读取 context 的值以及订阅 context 的变化。你仍然需要在上层组件树中使用 <MyContext.Provider> 来为下层组件提供 context

function Counter(){
  let {state,dispatch} = React.useContext(CounterContext);
  return (
      <div>
        <p>{state.number}</p>
        <button onClick={() => dispatch({type: 'add'})}></button>
        <button onClick={() => dispatch({type: 'minus'})}>-</button>
      </div>
  )
}
function App(){
    const [state, dispatch] = React.useReducer(reducer, {number:0});
    return (
        <CounterContext.Provider value={{state,dispatch}}>
          <Counter/>
        </CounterContext.Provider>
    )
}

useEffect

  • 在函数组件主体内(这里指在 React 渲染阶段)改变 DOM、添加订阅、设置定时器、记录日志以及执行其他包含副作用的操作都是不被允许的,因为这可能会产生莫名其妙的 bug 并破坏 UI 的一致性
  • 使用 useEffect 完成副作用操作。赋值给 useEffect 的函数会在组件渲染到屏幕之后执行。
  • useEffect 就是一个 Effect Hook,给函数组件增加了操作副作用的能力。它跟 class 组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 具有相同的用途,只不过被合并成了一个 API
  • 该 Hook 接收一个包含命令式、且可能有副作用代码的函数
 function Counter() {
 	console.log('Counter render')
    const [number, setNumber] = React.useState(0);
    React.useEffect(() => {
        console.log('开启一个新的定时器')
        const $timer = setInterval(() => {
        	console.log('执行定时器')
            setNumber(number => number  1); // 用依赖前一个状态的函数,避免闭包setNumber(number1)number一直是定义时的值0
        }, 1000);
        return () => {
            console.log('销毁老的定时器');
            clearInterval($timer);
        }
    });
    return (
        <p>{number}</p>
    )
}
ReactDOM.render(<Counter />, document.getElementById('root'));
/**
这是最难用好的一个effect
每次渲染都会开启一个新的定时器
1、加上依赖【】数组。定时器只会执行一次
2、可在下一次执行effect之前清除上一个定时器

Counter render
开启一个新的定时器
执行定时器
Counter render
销毁老的定时器
开启一个新的定时器
执行定时器
*/
export function useEffect(callback,dependencies){
	// 先判断是不是初次渲染
  if(hookStates[hookIndex]){
      let [lastCallback,lastDeps] = hookStates[hookIndex];
      let same = dependencies&&dependencies.every((item,index)=>item === lastDeps[index]);
      if(same){
        hookIndex++;
      }else{
        // 如果有任何一个值不一样,则执行上一个的销毁函数
        lastCallback&&lastCallback();
        // 开启一个新的宏任务
        setTimeout(()=>{
            lastCallback = callback();
            hookStates[hookIndex++]=[lastCallback,dependencies];
        });
      }
  }else{
 		// 如果是第一次执行 执行到此
    setTimeout(()=>{
 		let destroy = callback();	
        hookStates[hookIndex++]=[callback(),dependencies];
    });
  }
}

useLayoutEffect+useRef

  • 其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect
  • useEffect不会阻塞浏览器渲染,而 useLayoutEffect 会阻塞浏览器渲染
  • useEffect会在浏览器渲染结束后执行,useLayoutEffect 则是在 DOM 更新完成后,浏览器绘制之前执行
    在这里插入图片描述

forwardRef+useImperativeHandle

  • forwardRef将ref从父组件中转发到子组件中的dom元素上,子组件接受props和ref作为参数
  • useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值
function Child(props, ref) {
    const inputRef = React.useRef();
    // 这个方法可以定制暴露给父组件ref值,forwardRef.current = {focus}
    React.useImperativeHandle(ref, () => (
        {
            focus() {
                inputRef.current.focus();
            }
        }
    ));
    return (
        <input type="text" ref={inputRef} />
    )
}
function Parent(){
	let [number, setNumber] = React.useState(0);
	const inputRef = React.useRef();
	function getFocus() {
        console.log(inputRef.current);
        inputRef.current.value = 'focus';
        inputRef.current.focus();
    }
    return (
        <div>
            <ForwardChild ref={inputRef} />
            <button onClick={getFocus}>获得焦点</button>
            <p>{number}</p>
            <button onClick={() => {
                debugger
                setNumber( number + 1)
            }}>+</button>
        </div>
    )
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值