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 的作用、概念,并提供详细的例子解释 都有详细的解释,这里不再赘述。


完!

相关推荐
m0_7482309413 分钟前
Rust赋能前端: 纯血前端将 Table 导出 Excel
前端·rust·excel
qq_5895681021 分钟前
Echarts的高级使用,动画,交互api
前端·javascript·echarts
黑客老陈1 小时前
新手小白如何挖掘cnvd通用漏洞之存储xss漏洞(利用xss钓鱼)
运维·服务器·前端·网络·安全·web3·xss
正小安1 小时前
Vite系列课程 | 11. Vite 配置文件中 CSS 配置(Modules 模块化篇)
前端·vite
暴富的Tdy2 小时前
【CryptoJS库AES加密】
前端·javascript·vue.js
neeef_se2 小时前
Vue中使用a标签下载静态资源文件(比如excel、pdf等),纯前端操作
前端·vue.js·excel
m0_748235612 小时前
web 渗透学习指南——初学者防入狱篇
前端
℘团子এ2 小时前
js和html中,将Excel文件渲染在页面上
javascript·html·excel