发布于 2026-01-06 1 阅读
0

React Hooks 的工作原理是什么?DEV 全球展示挑战赛,由 Mux 主办:展示你的项目!

React Hooks 的工作原理(简单来说)?

由 Mux 赞助的 DEV 全球展示挑战赛:展示你的项目!

我们先来了解一下什么是钩子?

Hook 是一种 React 函数,它允许你在基于函数的组件中使用状态和 React 特性。Hook 让你可以直接使用函数,而无需在 HOC、类和函数之间来回切换。由于 Hook 是普通的 JavaScript 函数,因此你可以使用内置的 Hook,也可以创建自定义的 Hook。这样,你的问题现在只需一行代码就能解决。

在了解 React hooks 的工作原理之前,让我们先来定义一下什么是闭包。

“闭包是指即使函数在其词法作用域之外执行,它仍然能够记住并访问其词法作用域。”

接下来的示例中将会出现闭包,所以我需要对它进行定义。

useState 的工作原理是什么?

为了说明 useState 的工作原理,我们来看一个例子。

const OurReact = (function(){
    let val; // ‘val’ stores the value in the module scope 
        return {
            render(Component) {
               const Comp = Component();
               Comp.render();
               return Comp;
            },
            useState(initialVal) {
               val = val || initialVal;// Assigns a new value every run 
               function setState(newVal) {
                   val = newVal;
               }
               return [val, setState];
            },
        };  
})();
Enter fullscreen mode Exit fullscreen mode

在上面的示例中,我们使用了模块模式来创建我们自己的小型 React 克隆项目。因此,如果您想访问 useState,则需要使用 `OurReact.useState(...)`。与 React 类似,为了跟踪组件状态,我们使用了 `val` 变量(为了简单起见,它只跟踪一个组件)。如您所见,`useState` 位于一个闭包中。现在让我们使用上面的代码。

function Character () {
    const [characteName, setCharacterName] = OurReact.useState(Mario); // default value to ‘Mario’
    return{
        changeName: (charName) => setCharacterName(charName),
        render: () => console.log(Rendered character:, characteName),
    }
}
let App;
App = OurReact.render(Character);  // Rendered character: Mario
App.changeName(Luigi);
App = OurReact.render(Character); // Rendered character: Luigi
Enter fullscreen mode Exit fullscreen mode

这是用于演示 useState hook 的简单代码。

那么 useEffect 是如何工作的呢?

当你想在每次渲染后执行一些副作用(例如调用 API 或检查组件是否已获取数据)时,可以将这些副作用传递给 `useEffect`。如果你熟悉基于类的组件,那么 `useEffect` 的作用与 React 类中的 `componentDidMount`、`componentDidUpdate` 和 `componentWillUnmount` 类似,只是将这三个方法统一到一个 API 中。

function Example() {
    const [characterName, setCharacterName] = useState(‘Mario’);
    // Similar to componentDidMount and componentDidUpdate:
    useEffect(()=>{
       document.title = `Character name ${characterName}`;
    });
    return(
       <div>
          <p>Character : {characterName}</p>
          <input type=’text’ value={characterName} onChange={e => setCharacterName(e.target.value)} />
       </div>
    );
};

Enter fullscreen mode Exit fullscreen mode

让我们通过扩展之前创建的小型 React 克隆版本来复制 useEffect hook。

const OurReact = (function(){
    let val, deps; // A new variable ‘deps’ to hold our dependencies
    return {
        render(Component) {
            const Comp = Component();
            Comp.render();
            Return Comp;
        },
        useEffect(callBack, dependencyArr){
           const hasNoDependency = !dependencyArr,
                 hasDependencyChanged = deps ? !dependencyArr.every((el, i) => el === deps[i]) : true;
           if (hasNoDependency  || hasDependencyChanged ) {
               callback();
               deps = dependencyArr;
           }
        },
        useState(initialVal) {
           val = val || initialVal;
           function setState(newVal) {
              val = newVal;
           };
           return [val, setState];
        },
    };  
})();

Enter fullscreen mode Exit fullscreen mode

用法:

function Character () {
    const [characteName, setCharacterName] = OurReact.useState(Mario);
    OurReact.useEffect(() => {
        console.log(effect called , characterName);
    }, [characteName])
    return{
        changeName: (charName) => setCharacterName(charName),
        noopFunction: () => setCharacterName(characteName),  // Similar to Jquery.noop that does nothing.
        render: () => console.log(Rendered character:, characteName),
    }
}

let App;
App = OurReact.render(Character);
//   effect called Mario
// Rendered character: Mario

App.changeName(Luigi);
App = OurReact.render(Character); 
// effect called Luigi
// Rendered character: Luigi

App.noopFunction()
App = OurReact.render(Character); 
// No effects
// Rendered character: Luigi

App.changeName(Yoshi);
App = OurReact.render(Character); 
// effect called Yoshi
// Rendered character: Yoshi

Enter fullscreen mode Exit fullscreen mode

useEffect 会在依赖项发生变化时运行,因此我们在上面的示例中引入了一个名为“deps”的新变量。以上就是 useEffect Hook 的简要说明。

我们知道 Hook 的实现包含数千行代码。但希望你已经对它的内部工作原理有了一定的了解。

使用 Hooks 时需遵守的规则。

  • 钩子应该始终在最高级别使用。

遵循这条规则,可以确保每次组件渲染时,Hooks 函数都会按照声明时的顺序被调用。(记住,千万不要在嵌套函数内部或循环内部调用 Hooks 函数。

解释:

functions character() {
    const [characterName, setCharacterName] = useState(Mario);
    useEffect(function storeTheCharacter(){
        localStorage.setItem(formData, characterName);
    });
    const [characterAbility, setCharacterAbility] = useState(Fire flower); 
    useEffect(function displayDetails(){
    document.getElementById(characterView).innerHTML(`Character: ${characterName}, Ability: ${ characterAbility}`)
    });
}
Enter fullscreen mode Exit fullscreen mode

我们可能会问,React 如何知道哪个状态对应哪个 useState?答案正如我们之前讨论的,我们需要始终按照声明顺序调用 hooks。如果我们在循环中调用 hooks,或者 hook 的调用顺序发生变化,React 就会搞不清楚如何维护组件的状态。

// 1st Render
useState(Mario);  // Initialize the ‘characterName’ state variable to ‘Mario’
useEffect(storeTheCharacter);  // Adding an effect to store the ‘characterName’ to the localStorage
useState(Fire Flower);  // Initialize the ‘characterAbility’ state variable with 'Active'
useEffect(displayDetails);  // Adding an effect to update the displaying data

// 2nd render
useState(Mario);  // Read the characterName state variable (argument is ignored)
useEffect(storeTheCharacter);  // Replace the effect for persisting the form
useState(Fire Flower);  // Read the characterAbilities state variable (argument is ignored)
useEffect(displayDetails);  // Replace the effect for updating the displaying data

Enter fullscreen mode Exit fullscreen mode

由于钩子的顺序得以保留,React 将能够维护我们组件的状态。

如果我们调用一个带有条件的钩子函数呢?

if( characterName !== ‘’ ){
    useEffect(function storeTheCharacter () {
            localStorage.setItem('formData', characterName);
    });
}

Enter fullscreen mode Exit fullscreen mode

这里我们违反了 Hook 在条件判断中的第一条规则。让我们看看当条件为“false”时会发生什么,渲染过程中会跳过 Hook,并且 Hook 调用的顺序也会发生变化。
例如:

useState(Mario) // 1. Read the name state variable (argument is ignored)
// useEffect(storeTheCharacter)  // This Hook was skipped!
useState(Fire Flower)  // 2 (but was 3). Fail to read the surname state variable
useEffect(updateTitle)  // 3 (but was 4). Fail to replace the effect
Enter fullscreen mode Exit fullscreen mode

React 无法识别第二个 useState Hook 调用应该返回什么。React 预期此组件中的第二个 Hook 调用对应于 'storeTheCharacter' effect,就像上次渲染时一样,但现在并非如此。从那时起,我们跳过的那个 Hook 调用之后的所有后续调用都会向前偏移一位,从而导致错误。这就是为什么 Hook 总是在组件的顶层调用的原因。

  • Hooks 应该始终从 React 函数中调用。

不要从普通的 JavaScript 函数中调用 Hooks。相反,你可以……

  1. 从 React 函数组件调用 Hooks。
  2. 调用自定义钩子。

解释:

import { useState } from react;
const lives = 3;
const isGameOver = (noOfDeaths) =>{
    const livesLeft = lives  noOfDeaths;
    const [characterName, setCharacterName] = useState(Mario);
        if (livesLeft === 0) { return Game Over’’; } else { return Continue; }
}

Enter fullscreen mode Exit fullscreen mode

在普通函数内部调用 useState 是没有任何意义的。

以上就是使用钩子时需要遵守的规则。


希望大家对 React Hooks API 的工作原理以及使用 Hooks 时需要遵循的规则有了清晰的了解。如今,Hooks 已成为大多数 React 组件的关键组成部分。如果大家对本文有任何疑问,欢迎在评论区留言。谢谢!

文章来源:https://dev.to/akshaypalekar15/how-react-hooks-work-in-simple-words-4d4d