React4——Hook 含部分原理

本文详细介绍了React Hooks的使用,包括useState进行状态管理,useEffect处理副作用,特别是需要清除和不需要清除的情况。还讨论了自定义Hook的创建,以及为什么不能在循环、条件或嵌套函数中调用Hook的原因,揭示了Hook实现背后的Array和Cursor机制。

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

React Hook

Hook 是 React 16.8 的新增特性。✌️

Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数。

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

它可以从组件中提取状态逻辑 使这些逻辑可以单独测试和复用。(自定义hook)

基础Hook

useState

作用是设置初始state,返回以及更新state

import React, { useState } from 'react’;

function ExampleWithManyStates() {
  // initialState具有惰性,只在初始渲染时调用
  // useState 会返回一对值:当前状态(count)和一个让你更新它的函数(setCount)
  const [count, setCount] = useState(initialCount);
  // 可以声明多个 state 变量😯
  const [age, setAge] = useState(42);
  
  return (    
    <>
    <!-- 直接使用count 无需this.state -->
      Count: {count}
		<!-- 更新 state的方法 -->
      <button onClick={() => setCount(initialCount)}>Reset</button>
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
      <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
      <button onClick={() => setCount(setAge => setAge + 1)}>{age}</button>
    </>
    );
}
  • useState后的参数 (initialState) 是state的初始值,它是惰性调用,只在初始渲染时起效

  • useState会返回一对值:当前状态一个让你更新它的函数,这个第二个返回值类似 class 组件的 this.setState,但是它不会把新的 state 和旧的 state 进行合并

useEffect

useEffect用来执行副作用操作;

它会发生在每轮渲染结束后执行;

它用来解决函数组件主体内(React 渲染阶段)无法执行副作用操作(改变 DOM、添加订阅、设置定时器、记录日志等等)的问题。

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新 
// count是可选参数,当两次渲染没有变化发生时就跳过对effect的调用
  • 可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。
  • 与 componentDidMount 或 componentDidUpdate 不同的是使用 useEffect 调度的 effect 不会阻塞浏览器更新屏幕,这让应用看起来响应更快。
  • 两种常见的副作用操作分为: 需要清除和不需要清除的(即执行操作后就可以忽略)
不需要清除的副作用操作

替代componentDidMount 和 componentDidUpdate

拿class组件的写法和使用hook的函数组件的写法做比较:

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  componentDidMount() {
    document.title = `You clicked ${this.state.count} times`;
  }
  componentDidUpdate() {
    document.title = `You clicked ${this.state.count} times`;
  }
  
  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

可以改造成下面这样,优势在于,本来需要在组件加载和更新时执行相同的代码,使用useEffect Hook后只需写一遍

import React, { useState, useEffect } from 'react';
function Example() {
  const [count, setCount] = useState(0);



// 这样每次更新时都能在effect中取得最新的值
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
需要清除的副作用操作

替代componentDidMount 和 componentWillUnmount;

要在 effect 中返回一个函数,作为清除函数;

React 会在组件卸载的时候执行清除操作。

import React, { useState, useEffect } from 'react';

function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
   // 添加订阅
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    // Specify how to clean up after this effect:
   // 清除订阅
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });
  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

注意⚠️:

  • Hook可以在无需修改组件结构的情况下复用状态逻辑。
  • Hook 将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据),而并非强制按照生命周期划分。
  • 你只能在React函数最顶层使用Hook
  • Hook 是一种复用状态逻辑的方式,它不复用 state 本身
  • 不要在循环,条件或嵌套函数中调用 Hook(原因见后)
useContext

const value = useContext(MyContext);

  • 接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。
  • 调用了 useContext 的组件总会在 context 值变化时重新渲染。

自定义Hook

使用hook时,当我们想在两个函数之间共享逻辑时,我们会把它提取到第三个函数中,这就是一个自定义hook;
约定函数名以use打头
内部可以使用其他Hook
多个组件使用共同的Hook不会共享state,它们的state是完全独立的

import { useState, useEffect } from 'react';
// useFriendStatus就是一个自定义hook
function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });

  return isOnline;
}

如何使用这个自定义hook(useFriendStatus)?

function FriendStatus(props) {
  const isOnline = useFriendStatus(props.friend.id);

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

function FriendListItem(props) {
  const isOnline = useFriendStatus(props.friend.id);

  return (
    <li style={{ color: isOnline ? 'green' : 'black' }}>
      {props.friend.name}
    </li>
  );
}

其他Hook

  • useReducer
  • useCallback
  • useMemo
  • useRef
  • useImperativeHandle
  • useLayoutEffect
  • useDebugValue

React hooks——not magic, just Arrays;

上边提到,不要在循环,条件或嵌套函数中调用 Hook,原因如下,我们可以先从hooks的原理入手;

  • 当我们调用 useState 的时候,会返回形如 (变量, 函数) 的一个元祖。并且 state 的初始值就是外部调用 useState 的时候,传入的参数。
  • 然后,useState 返回的用于更改状态的函数,自动调用了render方法来触发视图更新。
  • 可以思考,首先需要一个全局变量记录hooks初始状态,然后在方法内部调用一个类似于setState的方法来更新状态,最后调用render方法来更新视图,返回[state, setState]这个元祖。具体代码如下所示:
function render() {
  ReactDOM.render(<App />, document.getElementById("root"));
}

let state: any;

function useState<T>(initialState: T): [T, (newState: T) => void] {
  state = state || initialState;

  function setState(newState: T) {
    state = newState;
    render();
  }

  return [state, setState];
}

render(); // 首次渲染

但是现实中,我们的 useState是支持多个 state的。它的本质上是通过一个朴实无华的 Array 对象来实现的。具体过程如下(需要一个全局数组states[]和一个记录位置的cursor):

  • 第一次渲染时候,根据 useState 顺序,逐个声明 state 并且将其放入全局 Array 中。每次声明 state,都要将 cursor 增加 1。
  • 更新 state,触发再次渲染的时候。cursor 被重置为 0。按照 useState 的声明顺序,依次拿出最新的 state 的值,视图更新。
import React from "react";
import ReactDOM from "react-dom";

const states: any[] = [];
let cursor: number = 0;

function useState<T>(initialState: T): [T, (newState: T) => void] {
  const currenCursor = cursor;
  states[currenCursor] = states[currenCursor] || initialState; // 检查是否渲染过

  function setState(newState: T) {
    states[currenCursor] = newState;
    render();
  }

  ++cursor; // update: cursor
  return [states[currenCursor], setState];
}

function App() {
  const [num, setNum] = useState < number > 0;
  const [num2, setNum2] = useState < number > 1;

  return (
    <div>
      <div>num: {num}</div>
      <div>
        <button onClick={() => setNum(num + 1)}>1</button>
        <button onClick={() => setNum(num - 1)}>1</button>
      </div>
      <hr />
      <div>num2: {num2}</div>
      <div>
        <button onClick={() => setNum2(num2 * 2)}>扩大一倍</button>
        <button onClick={() => setNum2(num2 / 2)}>缩小一倍</button>
      </div>
    </div>
  );
}

function render() {
  ReactDOM.render(<App />, document.getElementById("root"));
  cursor = 0; // 重置cursor
}

render(); // 首次渲染
那为什么不能在循环,条件或嵌套函数中调用 Hook呢?

这与它的cursor相关:由于 useState 是基于 Array+Cursor 来实现的,第一次渲染时候,state 和 cursor 的对应关系在条件改变时可能发生变化,但是函数并不能知道这种变化,所以可能会造成错位,从而导致一些问题。如下例:

// ------------
// 第一次渲染----满足条件
// ------------
useState('1') // cursor为1
useState('2') // cursor为2
 
// 我们在条件语句中使用Hook
if (case) {
 useState('3')  // cursor为3
}
useState('4') // cursor为4

// -------------
// 第二次渲染---不满足条件
// -------------
useState('1') // cursor为1
useState('2') // cursor为2
 
// 我们在条件语句中使用Hook
if (case) {
 useState('3') 
}
useState('4') // cursor为3❌ 造成接受的变量赋值错位

最后:
总的来说,Hooks 对代码编写的要求较高,在没有有效机制保证代码可读性、规避风险的情况下,Class 依然是首选

### React 工作原理深入解析 React 是一种用于构建用户界面的声明式 JavaScript 库,其设计目标是让开发者可以轻松创建复杂的 UI 组件并保持高效的更新能力。以下是关于 React 的工作原理及其内部机制的详细分析。 #### 1. JSX 到虚拟 DOM 的转换 在 React 中,JSX 并不是一个真正的 HTML 结构,而是一种语法糖,在编译阶段会被转化为普通的 JavaScript 对象表示形式——即虚拟 DOM(Virtual DOM)。例如,在 React 17 及之后版本中,`import React from "react"` 和 `import ReactDOM from "react-dom"` 不再强制需要显式导入[^1],因为 Babel 或其他工具链会在背后自动完成这些操作。这种优化简化了开发者的配置过程,同时也提升了项目的可维护性和兼容性。 #### 2. React Fiber 架构的核心作用 为了提升性能和用户体验,React 在 v16 版本引入了一种全新的协调算法架构——Fiber。它基于协程的思想实现了任务分片的能力,允许浏览器在执行高优先级的任务时中断当前正在进行中的低优先级任务,并稍后再继续未完成的工作。这不仅提高了应用对于复杂场景下的响应速度,还有效减少了长时间运行的操作可能引发的卡顿现象[^2]。 #### 3. Hooks 的底层实现逻辑 Hooks 提供了一个无需依赖类组件即可访问状态和其他功能的方式,极大地增强了函数组件的功能范围。其实现方式本质上利用闭包保存变量的状态信息并通过调度器来管理生命周期方法调用顺序等问题。比如当使用 `useState` 定义某个状态值时,实际上是在内部注册了一系列回调函数以便于后续触发重新渲染等行为;而对于像 `useEffect` 这样的副作用 Hook,则会记录下哪些部分应该被清理以及何时再次激活它们等等。 #### 4. Server-Side Rendering (SSR) 原理概述 除了客户端渲染外,React 支持服务端渲染以改善首屏加载时间和服务质量。具体来说就是先由 Node.js 环境生成静态HTML字符串发送给前端展示出来然后再接管剩余交互逻辑的过程称为 SSR 。这一过程中涉及到模板引擎的选择、数据预取策略制定等多个方面考虑因素[^4]。通过这种方式不仅可以加快页面呈现的速度还可以增强 SEO 效果。 ```javascript // 示例代码:简单的 SSR 渲染流程模拟 const express = require('express'); const app = express(); const path = require('path'); app.get('/', async (req, res) => { const InitialData = await fetchData(); // 获取初始数据 let context = {}; const htmlString = renderToString(<App initialData={InitialData} />, {context}); if(context.url){ return res.redirect(301, context.url); } res.send(` <!DOCTYPE html> <html lang="en"> ... ${htmlString} </html> `); }); function App({initialData}) { return ( <div>{JSON.stringify(initialData)}</div> ); } app.listen(8080, ()=> console.log("Server started on port 8080")); ``` 以上是对 React 核心工作机制的一个全面总结,涵盖了从基础概念到高级特性的多个层面的知识点。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值