对于使用hook大家都有一定的经验,能够很好地使用React强大的能力。我正好深入学习了一下,可能理解的不对,还请指正。
这个时候往往会有好几个版本的定义。
Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数。Hook 不能在 class 组件中使用 ——这使得你不使用 class 也能使用 React。
官网如是说。
从阮老师的博客中,也提到:
React Hooks 的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来。 React Hooks
就是那些钩子。 你需要什么功能,就使用什么钩子。React 默认提供了一些常用钩子,你也可以封装自己的钩子。
其实就是为了加强函数组件,让开发在使用函数组件的时候,能够摆脱类的束缚,但是又能够使用到类组件中的state以及生命周期相关的东西。
useState
最简单的用法就是:
function Counter() {
var [count, setCount] = useState(0);
return (
<div>
<div>{count}</div>
<Button onClick={() => { setCount(count + 1); }}>
点击
</Button>
</div>
);
}
根据其用法,可以使用闭包自定义一个uesState
:
var _state; // 把 state 存储在外面
function useState(initialValue) {
_state = _state || initialValue; // 如果没有 _state,说明是第一次执行,把 initialValue 复制给它
function setState(newState) {
_state = newState;
render();
}
return [_state, setState];
}
useEffect
useEffect
最常见的用法就是初始化用于获取数据:
useEffect(() => {
getData();
}, []);
特点:
let _deps; // _deps 记录 useEffect 上一次的 依赖
function useEffect(callback, depArray) {
const hasNoDeps = !depArray; // 如果 dependencies 不存在
const hasChangedDeps = _deps
? !depArray.every((el, i) => el === _deps[i]) // 两次的 dependencies 是否完全相等
: true;
/* 如果 dependencies 不存在,或者 dependencies 有变化*/
if (hasNoDeps || hasChangedDeps) {
callback();
_deps = depArray;
}
}
前面利用闭包存储状态,同样,多个useState
也会存储在一个专门的全局变量中,而如文中提到的,仅仅是用Array
实现而已。
useState
顺序,逐个声明 state
并且将其放入全局 Array
中。每次声明 state
,都要将 cursor
增加 1。state
,触发再次渲染的时候。cursor
被重置为 0。按照 useState
的声明顺序,依次拿出最新的 state
的值,视图更新。以这段代码为例:
function RenderFunctionComponent() {
const [firstName, setFirstName] = useState("Rudi");
const [lastName, setLastName] = useState("Yardley");
return (
<Button onClick={() => setFirstName("Fred")}>Fred</Button>
);
}
创建两个空的数组:setters
和state
,
设置cursor
为0
第一次运行时,每次useState()
调用,都会将setter
函数(绑定到游标位置)推到setter
数组上,然后将某些状态推到state
数组上。
每个setter
都有一个指向其光标位置的引用,因此通过触发对任何setter
的调用,它将更改state
数组中该位置的状态值。
let state = [];
let setters = [];
let firstRun = true;
let cursor = 0;
function createSetter(cursor) {
return function setterWithCursor(newVal) {
state[cursor] = newVal;
};
}
// This is the pseudocode for the useState helper
export function useState(initVal) {
if (firstRun) {
state.push(initVal);
setters.push(createSetter(cursor));
firstRun = false;
}
const setter = setters[cursor];
const value = state[cursor];
cursor++;
return [value, setter];
}
// Our component code that uses hooks
function RenderFunctionComponent() {
const [firstName, setFirstName] = useState("Rudi"); // cursor: 0
const [lastName, setLastName] = useState("Yardley"); // cursor: 1
return (
<div>
<Button onClick={() => setFirstName("Richard")}>Richard</Button>
<Button onClick={() => setFirstName("Fred")}>Fred</Button>
</div>
);
}
// This is sort of simulating Reacts rendering cycle
function MyComponent() {
cursor = 0; // resetting the cursor
return <RenderFunctionComponent />; // render
}
console.log(state); // Pre-render: []
MyComponent();
console.log(state); // First-render: ['Rudi', 'Yardley']
MyComponent();
console.log(state); // Subsequent-render: ['Rudi', 'Yardley']
// click the 'Fred' button
console.log(state); // After-click: ['Fred', 'Yardley']
那么,React文档中提到hook时,有两个额外的规则又是为什么呢?
那假设这么使用:
let firstRender = true;
function RenderFunctionComponent() {
let initName;
if(firstRender){
[initName] = useState("Rudi");
firstRender = false;
}
const [firstName, setFirstName] = useState(initName);
const [lastName, setLastName] = useState("Yardley");
return (
<Button onClick={() => setFirstName("Fred")}>Fred</Button>
);
}
上述代码中,在条件语句中使用useState
,看看结果:
可以看出来,第二次渲染时,绕过了条件语句里的内容,因此state
对应不上,导致了错误的结果。
讲了这么多,那实际工作中怎么自定义hook呢?
首先可以参考官网的文档,https://zh-hans.reactjs.org/docs/hooks-overview.html#building-your-own-hooks。
这边我再班门弄斧,简单说明一下自我的理解。
以一个简单的产品列表为例:
const useProduct = ({
offset,
limit,
searchValues,
}) => {
const [products, setProducts] = useState({
items: [],
total: 0,
});
const fetchProducts = useCallback(async () => {
const result = await getProducts({
$offset: offset,
$limit: limit,
searchValues,
});
setProducts(result);
}, [
offset,
limit,
searchValues,
]);
const removeProduct = useCallback(
async (productId) => {
await deleteProduct(productId);
fetchProducts();
},
[fetchProducts],
);
useEffect(() => {
fetchProducts();
}, [fetchProducts]);
return [
products,
{
fetchProducts,
removeProduct,
},
];
};
可以理解为数据的操作都在hook里进行,而外部只关心自己想要的。我只要数据列表,获取产品钩子(可能并不需要,可通过参数变更从而触发重新获取数据)、删除产品钩子,此外有可能还会有修改产品的。
代码可能有所缺陷,还请指出。
结束语
越学习越发现自己的不足。目前刚开始用React,自知算不上了解,还有很深的水,也希望能够有机会多深入学习学习。
参考链接:
Deep dive: How do React hooks really work?
React hooks: not magic, just arrays
useEffect 完整指南
React Hooks 原理
React Hooks 入门教程