介绍
特点
- 声明式编程
- 组件化:复用性强
- 跨平台编写:只需要修改渲染器
组件
Class 组件
使用继承和构造函数编写,具有 this 和生命周期和独特的 render () 方法
函数式组件
没有生命周期,需要借助 Hook,其需要返回一个 JSX。
对比:
函数式组件比起 Class 组件编码量减少,没有复杂的生命周期。支持自定义 hook,逻辑复用更方便。
组件和 Hook 的关系
UI 被拆分为多个独立单元,这些单元组合可以构成多种视图展示,这就是组件(概念上的原子)。
Hook 贴近组件内部运行的各种概念逻辑,effect、state、context 等。Hook 更贴近于电子。
Hook 规则 & 原理
-
只能在最顶层使用 Hook。
React 中靠的是 Hook 的调用顺序判断哪个 State 对应哪个 useState。在第一次渲染后,会有一个 Hook 表,如果在条件判断中存在 Hook,且下一次渲染这个条件判断内部的 Hook 不调用,那么这个第二次渲染形成的 Hook 表和第一次的 Hook 表会在没有执行的 Hook 这里产生错位导致问题。
这和 Vue 当中对于常要更新的元素组使用 V-for 渲染必须使用 独立 key 而不是 index key 是类似的道理,因为 index 是按照顺序赋值的,每个元素的 key 会默认按顺序生成,如果其中有一个元素更新了,例如被删除了,那么其相邻的元素会顶替这个元素的 key(因为 index 是按顺序的),但其实这个相邻元素有自己的内部差异(比如有一个输入框内部含有文字),这样会产生渲染问题。
-
只能在 React 函数中调用 Hook,不能随意使用。
可以在 React 函数组件中或自定义 Hook 中调用 Hook,自定义的 Hook 必须以 use 开头,Hook 中的 state 是完全隔离的。
过期闭包问题
function createIncrement(i) {
let value = 0;
function increment() {
value += i;
console.log(`value : ${value}`);
const message = `value : ${value}`; // (*)
function logMessage() {
console.log(message);
}
return logMessage;
}
return increment;
}
const inc = createIncrement(1);
const log = inc(); // value : 1
inc(); // value : 2
inc(); // value : 3
log(); // value : 1,这个值过期了,最新的值是 3
简单的理解这个代码就是 createIncrement 会返回 increment 函数给 inc 变量,所有函数都有名为 [[Environment]] 的隐藏属性,该属性保存了对创建该函数的词法环境的引用。increment.[[Environment]] 有对 {value: 0, i: 1} 词法环境的引用。在定义 log 表达式函数时,执行了 inc ( 就是 increment 函数,此时 value = 0 + i = 1,并返回了内部 logMessage 函数,logMessage.[[Environment]] 有对 {value: 1} 词法环境的引用) 函数,log 保存了 logMessage函数。
我们在后续调用 inc 函数后更新了 value 值,但是其内部新的 logMessage 函数并没有被赋值给 log,所以在最后调用 log 函数后输出的就是第一次声明 log 表达式函数时 inc 函数第一次调用的 value 值,也就是 1。
“在 JS 中,函数运行上下文是由定义的位置决定的,当函数的闭包包住了旧的变量值是,就会出现过期闭包问题。”
React 怎么解决这个问题的?
只要是通过 useEffect 的第二个参数传入 count 值,这样可以在 count 更新时得到通知。不过赘述。
JS 怎么解决这个问题?
- 立即执行函数 我们可以对这里的函数逻辑进行一些改变。 如下代码块:
function createIncrement(i) {
let value = 0;
return function increment() {
value += i;
console.log(`value : ${value}`);
const message = `message : ${value}`; // (*)
(function logMessage() {
console.log(message);
})() // !!! 注意形式变化了
}
}
const inc = createIncrement(1);
inc(); // value : 1 \n message : 1
inc(); // value : 2 \n message : 2
我们可以发现,我们没有再将此时的 logMessage 函数提前赋值给一个变量,而是立即执行更新,所以不会再出现过期的变量。
-
改变 message 变量的声明位置为 logMessage 内部的变量,依赖于自己内部的变量去读取外部的词法环境。
function createIncrement(i) { let value = 0; return function increment() { value += i; console.log(`value : ${value}`); return function logMessage() { const message = `message : ${value}`; // (*) console.log(message); } } } const inc = createIncrement(1); const log = inc(); // value : 1 inc(); // value : 2 inc(); // value : 3 log(); // message : 3
这里的区别在于我们将 message 的声明移动到了 logMessage 函数的内部。因为 value 来自于 increment 函数,所以在执行 log 表达式函数时,logMessage 会在外部寻找 value 值,而此时 value 已经被修改为 3,所以打印的是最新的值。
区别就在于,最原本的代码中保留了 value = 1 的词法环境,而如今的没有保存,而是去寻找最新的 value 语法环境。