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];
},
};
})();
在上面的示例中,我们使用了模块模式来创建我们自己的小型 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
这是用于演示 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>
);
};
让我们通过扩展之前创建的小型 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];
},
};
})();
用法:
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
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}`)
});
}
我们可能会问,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
由于钩子的顺序得以保留,React 将能够维护我们组件的状态。
如果我们调用一个带有条件的钩子函数呢?
if( characterName !== ‘’ ){
useEffect(function storeTheCharacter () {
localStorage.setItem('formData', characterName);
});
}
这里我们违反了 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
React 无法识别第二个 useState Hook 调用应该返回什么。React 预期此组件中的第二个 Hook 调用对应于 'storeTheCharacter' effect,就像上次渲染时一样,但现在并非如此。从那时起,我们跳过的那个 Hook 调用之后的所有后续调用都会向前偏移一位,从而导致错误。这就是为什么 Hook 总是在组件的顶层调用的原因。
- Hooks 应该始终从 React 函数中调用。
不要从普通的 JavaScript 函数中调用 Hooks。相反,你可以……
- 从 React 函数组件调用 Hooks。
- 调用自定义钩子。
解释:
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’; }
}
在普通函数内部调用 useState 是没有任何意义的。
以上就是使用钩子时需要遵守的规则。
希望大家对 React Hooks API 的工作原理以及使用 Hooks 时需要遵循的规则有了清晰的了解。如今,Hooks 已成为大多数 React 组件的关键组成部分。如果大家对本文有任何疑问,欢迎在评论区留言。谢谢!
文章来源:https://dev.to/akshaypalekar15/how-react-hooks-work-in-simple-words-4d4d