React 暗号:Hook 一下,状态听话

117 阅读5分钟

一、React 创建组件

在 React 中,组件是构建用户界面的基本单元。主要有两种创建组件的方式:函数式组件function类组件class
hooks出现之前,函数式组件是无状态的,无法管理组件的状态。而类组件可以通过this.statethis.setState来管理状态。使用hooks后,函数式组件也可以管理状态。
函数式组件语法简洁,易于开发、理解和测试,适合简单的 UI 组件。类组件适合需要复杂状态管理和生命周期方法的场景,但由于大量使用this,可能会让人感到困惑。随着hooks的引入,函数式组件的功能和灵活性大大增强,成为现代 React 开发的主流选择。


二、hooks(钩子函数)

由 React 官方封装好的一系列函数,它们的用法和作用:

  1. useState —— 定义一个响应式变量,提供专门的方法修改该变量值。
import { useState } from "react";

function One(){
    let [num, setNum] = useState(1); // [1, func]
    // 响应式方法监听住这个 num,num 变化了,视图也会跟着变化

    function handle(num){
    // num++; // num 改了但是无法触发视图更新
    setNum(num + 1); // 负责将 num 修改为 参数值,并触发视图更新
    console.log(num);
  }

    return (
        <div>
            <button onClick={() => {handle(num)}}>{num}</button>
        </div>
    )
}
export default One;

image.png 2. useEffect —— 副作用函数,作为异步函数来执行。

  • 组件每次加载(挂载)就会触发;
import { useState, useEffect } from "react";

// 模拟一个接口请求函数
async function queryData() {
  const data = await new Promise((resolve)=>{
    setTimeout(() => {
      resolve(666);
    }, 2000);
  })
  return data
}

function One(){
    let [num, setNum] = useState(1); // [1, func]
    // useState 不支持异步代码
    // 异步代码可以使用 useEffect 来实现

    useEffect(()=>{
        console.log('useEffect');
        queryData().then((data)=>{
            setNum(data);
        })
    })

    return (
        <div>
            <button onClick={() => {}}>{num}</button>
        </div>
    )
}
export default One;

image.png

  • useEffect第二个参数为一个空数组时,只会在初次渲染(挂载)时触发;
    useEffect(()=>{
        console.log('useEffect');
        queryData().then((data)=>{
            setNum(data);
        })
    },[])

image.png

  • useEffect第二个参数为一个数组时,数组中传入一个变量时,该变量每次修改值都会带来useEffect的重新执行;
import { useState, useEffect } from "react";

// 模拟一个接口请求函数
async function queryData() {
  const data = await new Promise((resolve)=>{
    setTimeout(() => {
      resolve(666);
    }, 2000);
  })
  return data
}

function One(){
    let [num, setNum] = useState(1); // [1, func]
    let [age, setAge] = useState(18);// [18, func]

    useEffect(()=>{
        console.log('useEffect');
        queryData().then((data)=>{
            setNum(data);
        })
    },[age])

    return (
        <div>
            <button onClick={() => {}}>{num}</button>
            <h2 onClick={() => {setAge(age + 1)}}>{age}</h2>
        </div>
    )
}
export default One;

image.png

  • useEffect第一个参数是函数,该函数内部返回出来一个新的函数,新函数会在组件不展示(卸载) 时才触发。
import { useState, useEffect } from "react";

function One(){
    let [num, setNum] = useState(1); // [1, func]

    useEffect(() => {
        console.log('useEffect');
        let timer = setInterval(() => {
            console.log(num);
        }, 1000); // setInterval()按照指定的时间间隔重复调用函数或执行代码片段

        return () => {
            console.log('组件卸载');
            clearInterval(timer); // 组件卸载时停止定时器
        }
    }, [num])

    return (
        <div>
            <button onClick={(pre) => {setNum(pre + 1)}}>{num}</button>
        </div>
    )
}
export default One;

image.png 3. useLayoutEffect ——作为同步函数来执行。

维度useEffectuseLayoutEffect
执行时机浏览器绘制(paint)之后,异步调度DOM 更新完成后、浏览器绘制之前,同步执行
是否阻塞渲染否,不阻塞页面绘制是,会阻塞页面绘制直到回调结束
典型用途数据获取、订阅、日志、事件绑定等大多数副作用需要同步读取/修改 DOM 布局的场景:测量元素尺寸、位置,避免闪烁
性能影响影响小,可放心使用可能因阻塞渲染而降低帧率,需谨慎
服务端渲染可正常用SSR 下会报警告,通常需改成只在客户端执行
import { useLayoutEffect, useState } from "react"

function App(){
    const [num, setNum] = useState(1)
    // 当 num 值变更时,浏览器的那个 useLayoutEfect 中的 effect 函数执行完毕再渲染 
    // effect 会阻塞很久,超过 500ms 会导致掉帧
    useLayoutEffect(() => {
        console.log('useLayoutEffect');
        setNum(2);
    }, [num])

    return(
        <div>
            <button onClick={() => setNum(num + 1)}>{num}</button>
        </div>
    )
}
export  default App;
  1. useReducer —— 当修改 state 的逻辑比较复杂时,用useReducer
  • 传入的reducer函数中不能直接修改原state,必须要返回一个新对象
  • useReducer + immer 常常搭配使用。
import { useState, useReducer } from "react"
import { produce } from "immer"

function reducer(state, action){
    switch(action.type){
        case 'add':
            // return{
            //     result: state.result + action.num
            // }
            return produce(state, (state) => {
                state.a.b.c += action.num
            })

        case 'minus':
            return{
                result: state.result - action.num
            }
    }
}

function App() {
    // userReducer 接收的第二个参数 作为 reducer 的第一个参数
    // dispatch 接收的参数 作为 reducer 的第二个参数
    const[res, dispatch] = useReducer(reducer, {result: 0, a: {b: {c: 1, d: {e: 2}}}})
    // const[res, dispatch] = useReducer(reducer, {result: 0})
    const[num, setState] = useState({result: 0});

  return (
    <div>
        <h3>{res.a.b.c}</h3>
        {/* <h3>{res.result}</h3> */}
        <button onClick={() => dispatch({type: 'add', num: 2})}>+</button>
        <button onClick={() => dispatch({type: 'minus', num: 1})}>-</button>

        <h2>{num.result}</h2>
        <button onClick={() => setState({result: num.result + 2})}>+</button>
    </div>
  )
}

export default App

image.png 5. useRef —— 获取 DOM 结构。

import { useRef, useEffect } from 'react'
function App() {
    const ipt = useRef(null) // 创建一个 ref 对象 ipt,初始值为 null。
    useEffect(() => {
        // console.log(ipt.current); // 现在可以拿到 <input> 组件的引用
        ipt.current.focus() // // 调用原生 DOM 方法:让输入框自动获得焦点
    }, [])
    return(
        <div>
            <input type="text" ref={ipt}/> 
             {/* 将 ipt 这个 ref 绑定到 <input> 组件上。 */}
             {/* 组件挂载时,useEffect 会执行,ipt.current 指向 <input> 组件。 */}
        </div>
    )
}
export default App;

image.png 6. useContext 跨多层组件进行数据传递。实现 跨组件树的数据共享与实时更新

作用点说明
跨层级共享数据避免 “props drilling”(一级一级手动传参)。父组件只要在最外层包一个 <Provider>,任何后代组件(无论多深)都能直接拿到数据。
精确更新只有真正使用 useContext 的组件会在值变化时重新渲染;中间没用到 Context 的组件不会白白更新。
逻辑解耦UI 组件不再需要显式接收某些仅用于向下传递的 props,使组件树更扁平、更易重构。
import { createContext, useContext } from "react"

function Child2(){
    const count = useContext(numContext) 
    // 只要离我最近的那个 <numContext.Provider> 的 value 发生变化,就立刻把我重新渲染,并把最新的值赋给 count。
    // 成员收消息
    return(
        <div>
            <h3>孙子组件 -- {count}</h3>
        </div>
    )
    
}
function Child1(){
    const count = useContext(numContext)
    // 成员收消息
    return(
        <div>
            <h2>子组件 -- {count}</h2>
            <Child2/>
        </div>
    )
}

const numContext = createContext() // 建群
function App() {
    const num = 100
    return(
        <div>
            <numContext.Provider value={num}> 
            {/* 群主发消息  */}
                 <h1>父组件</h1>
                 <Child1/>
            </numContext.Provider>
        </div>
    )
}
export default App;

image.png