React & 解释常见的 hooks: useState / useRef / useContext / useReducer

前言

如果对 re-render 概念还不清楚,建议先看 React & 理解 re-render 的作用、概念,并提供详细的例子解释 再回头看本文。
如果对 React 基础语法还不熟练,建议先看 React & JSX 日常用法与基本原则 再回头看本文。

useState

useState 可以用来声明响应式数据。

使用案例:

js 复制代码
import ReactDOM from 'react-dom/client';
import { useState } from 'react';
const root = ReactDOM.createRoot(document.getElementById("root"));
const App = () => {
  const [name, setName] = useState('Jack')
  return <>
    <div>My name is {name}</div>
    <button onClick={() => setName('John')}>Change my name</button>
  </>
}
root.render(
  <App />
);

效果:

注意,state 更新采用异步执行 re-render,因此控制台打印的值仍然是旧的。

若希望能够在 state 变更后马上拿到最新的值,有两种方案:

  1. 使用临时变量存储要变更的值:
js 复制代码
// ...
const onChangeName = () => {
  const tempName = 'John'
   setName(tempName)
   console.log('My current name is', tempName)
 }
// ...
  1. 使用 useRef,下面会讲。

useRef

useRef 也是声明响应式数据的一种方式,与 useState 不同的是,它可以不受 re-render 的约束,在更新数据后能立刻访问最新的值。

使用案例:

js 复制代码
import ReactDOM from 'react-dom/client';
import { useRef } from 'react';
const root = ReactDOM.createRoot(document.getElementById("root"));
const App = () => {
  const name = useRef('Jack')
  const onChangeName = () => {
    name.current = 'John'
    console.log('My current name is', name.current)
  }
  return <>
    <div>My name is {name.current}</div>
    <button onClick={onChangeName}>Change my name</button>
  </>
}
root.render(
  <App />
);

效果:

可以看出 useRef useState 是两种相反的结果,控制台为最新,视图为旧。

在不强调视图的情况可以考虑用 useRef ,比如异步回调获取最新数据的场景。

useReducer

useReduceruseState 的进阶版,当 useState 操作逻辑变得复杂时,可以将它们提升为 useReducer 的书写方式 ,提高可读、维护性。

下面是 useEffect VS useReducer 相同功能的对比图:

useReducer 通过 switch 声明式结构,我们能一眼就能看出每个 type 都做了哪些事。

从对比图看,虽然没能展现 useReducer 的优势,但我们只要理解它的逻辑处理结构就行了,

多一种选择,何乐而不为呢。

在 useState 处理少量逻辑的情况下优先 useState,反之 useReducer

案例代码:

js 复制代码
import ReactDOM from 'react-dom/client';
import { useReducer } from 'react';
const root = ReactDOM.createRoot(document.getElementById("root"));

const todoReducer = (state, action) => {
  switch (action.type) {
    case 'add':
      return {
        ...state,
        todos: [
          ...state.todos,
          Math.floor(Math.random() * 1000)
        ]
      }
    case 'remove':
      return {
        ...state,
        todos: state.todos.splice(state.index)
      }
    default:
      return state
  }
}
const App = () => {
  const [state, dispatch] = useReducer(todoReducer, { todos: [] })
  return <>
    <header>Todos</header>
    <button onClick={() => dispatch({ type: 'add' })}>Add todo</button>
    <button onClick={() => dispatch({ type: 'remove' })}>Remove todo</button>
    <ul>
      {state.todos.map((todo) => (
        <li key={todo}>
          {todo}
        </li>
      ))}
    </ul>
  </>
}
root.render(
  <App />
);

参数解释:useReducer(reducer, initialArg, init?)

  • 第一个参数是一个 handler,即要处理数据的函数。
  • 第二个参数是默认值,和 useState(xx) 的第一个参数一样。
  • 第三个参数是一个 useMemo 回调函数(可选项),它可以缓存第二个参数的数据,避免 re-render 重复定义默认值。

useReducer 返回的 state / dispatch 表示:

  • state 获取我们的数据。
  • dispatch 触发 handler 函数

提示:掌握了 useReducer 等于学会了 react-redux 框架,它与 useReducer 的区别仅仅多了一层全局缓存的含义,对 react-redux 感兴趣的可参考:
React & 用一个简单案例体验一遍 React-dom & React-router & React-redux 全家桶

useContext

useContext 可以解决多层组件传递 props 数据的问题,如果你用过 Vue 的 provide/inject 函数,那你也就会了,它们俩的作用&概念是一致的。

案例代码:

js 复制代码
import ReactDOM from 'react-dom/client';
import { createContext, useContext, useState } from 'react';
const root = ReactDOM.createRoot(document.getElementById("root"));
const Profile = () => {
  const userInfo = useContext(UserInfoContext)
  return <>
    <label>Name: {userInfo.name}</label><br />
    <label>Age: {userInfo.age}</label><br />
    <label>Hobbies: 
      {userInfo.hobbies.map(hobby => <span key={hobby}>{hobby} </span>)}
    </label>
  </>
}
const ShoppingCart = () => {
  const userInfo = useContext(UserInfoContext)
  return <>
    <footer>
      Shopping Cart:
    </footer>
    <ul>
      {userInfo.carts.map((cart) => <li key={cart}>{cart}</li>)}
    </ul>
    Total num: {userInfo.carts.length }
  </>
}

const UserInfoContext = createContext(null)
const App = () => {
  const [userInfo] = useState({
    name: 'Jack',
    age: 30,
    hobbies: ['Running', 'Painting'],
    carts: ['Dog toy', 'Cup']
  })
  return <>
    <UserInfoContext.Provider value={userInfo}>
      <Profile/>
      <ShoppingCart/>
    </UserInfoContext.Provider>
  </>
}
root.render(
  <App />
);

效果:

useMemo / useCallback

useMemo 可以缓存非响应式数据,避免 re-render 的问题。

useCallback 可以缓存函数,避免 re-render 的问题。

对于函数缓存,虽然 useMemo 也能实现,但还是得额外嵌套一层函数,因此官方建议使用 useCallback

俩钩子的用法在 React & 理解 re-render 的作用、概念,并提供详细的例子解释 都有详细的解释,这里不再赘述。


完!

相关推荐
诗书画唱2 分钟前
【前端面试题】JavaScript 核心知识点解析(第二十二题到第六十一题)
开发语言·前端·javascript
excel9 分钟前
前端必备:从能力检测到 UA-CH,浏览器客户端检测的完整指南
前端
前端小巷子15 分钟前
Vue 3全面提速剖析
前端·vue.js·面试
悟空聊架构22 分钟前
我的网站被攻击了,被干掉了 120G 流量,还在持续攻击中...
java·前端·架构
CodeSheep23 分钟前
国内 IT 公司时薪排行榜。
前端·后端·程序员
尖椒土豆sss27 分钟前
踩坑vue项目中使用 iframe 嵌套子系统无法登录,不报错问题!
前端·vue.js
遗悲风28 分钟前
html二次作业
前端·html
江城开朗的豌豆31 分钟前
React输入框优化:如何精准获取用户输入完成后的最终值?
前端·javascript·全栈
CF14年老兵32 分钟前
从卡顿到飞驰:我是如何用WebAssembly引爆React性能的
前端·react.js·trae
画月的亮35 分钟前
前端处理导出PDF。Vue导出pdf
前端·vue.js·pdf