最近看完了《React.js 小书》
,了解了使用高阶组件把不同组件从context
上下文中取出来的方式,就构成了redux
的核心内容。很多组件只需要用高阶组件包裹一下,就具备了使用某些状态的特性。
不过这样的话,需要把组件重新整理,《React.js》
小书的最后,将所有组件都拆分成了无状态组件和为无状态组件附加状态的高阶组件。这样实际上就是把所有组件都重新整理,而且会使代码难以理解。
在我刚看完《React.js》
的时候,问前端高手说我现在高阶组件终于可以运用自如了,大佬简简单单的说,去看一下Hook
,比用HOC
要简单方便。于是咱只能老老实实把React Hook
加入到小本子上的To-Learn-List里,这里就来记录一下React Hook
的自学心得。
函数式组件 VS 类组件
在看官网的教程时候,官网有句话是:Hook
使你在非 class
的情况下可以使用更多的 React
特性。这让我想起一直以来的函数式组件和类组件的使用方式。高程4中说JS
的类实际上就是函数的语法糖,所以函数式组件和类组件本质上没有什么区别。只不过函数式组件不太好写state
,只方便使用props
,由于函数本身的特性,也易于写出无状态组件。类则一般都使用state
,但是本质上二者没有区别。
State Hook
- 在函数中引入state
根据前边那句粗体字,State Hook
就是在非类组件中可以使用state
特性,也就是钩入了React
的state
特性。
官网的例子很简单但也很说明问题:
import React, { useState } from 'react'
import ReactDOM from 'react-dom'
import './index.css'
function Test() {
const [count, setCount] = useState(0);
return (
<div>
<p> You clicked {count} times</p>
<button onClick={() => {
setCount(count + 1);
}}>Click Me
</button>
</div>
);
}
ReactDOM.render(
<Test/>,
document.getElementById('root')
);
这其中的useState
函数就是一个Hook
,使用一个初始值调用这个函数,函数会返回两个值,第一个是初始值对应的变量,第二个是修改这个变量的函数。在例子里,返回的count
变量的值是0
,而更改count
变量的函数被命名为setCount
。
有了变量和修改变量的方式,就会发现这很像state
与对应的setState
方法,从表面上看确实如此,所以后边的代码中直接渲染了count
变量,修改count
变量的时候使用事件给count+1
,不过这里实际上并没有一个state
对象,也不会像state
对象一样,把某个count
变量“合并”到一个state
对象中。初始的state
参数只会在第一次渲染的时候被使用到。
例子里使用了一个0
作为初始的参数,当然也可以是其他类型,或者就是一个对象,这个时候就能看出来区别:
function Test() {
const [count, setCount] = useState({
name: "cony",
age: 6
});
return (
<div>
<p> You see {count.name}.</p>
<p> {count.name} is {count.age} years old</p>
<button onClick={() => {
setCount({
name: "Jenny"
});
}}>Click Me
</button>
</div>
);
}
点击按钮之后,会发现原来渲染的6
消失了,这说明不是合并了到了count
变量指向的对象中,而是直接取代了原来的count
变量。
所以我们可以把这种通过useState
生成的变量叫做state
变量,操作这些变量就像是操作原本的this.state
中某一个具体的属性一样。
使用多个state
变量
那么很显然,只有一个state
变量未必能满足需求,所以可以反复调用useState
函数来生成多个state
变量,这样就可以独立使用。
这里还有一个特点,官网原话:React
假设当你多次调用 useState
的时候,你能保证每次渲染时它们的调用顺序是不变的。也就是会按照生成变量的顺序进行渲染。
这里要注意的是,生成变量之后,变量就彼此独立了,这个和之前说的useState
函数的特性相同。
我自己写了段小代码来试验一下:
function Test() {
const [first, setFirst] = useState(10);
const [second, setSecond] = useState(first * 3);
const [third, setThird] = useState(second + 10);
return (
<div>
<p> At start {first} * 3 = {second}.</p>
<p>Then {second} + 10 = {third}</p>
<button onClick={() => {
setFirst(first + 1);
}}>Add 1 to first
</button>
</div>
);
}
这里在生成三个变量的时候,在运行第二行程序时候,已经有了first
变量,依次类推,所以显示的结果是正确的,但是通过按钮给first+1
的时候,second
和third
的值不会变动,这种实际上也和操作this.state
的独立变量是一样的。
Effect Hook
- 在函数中引入副作用
除了this.state之外,在没有Hook之前的React中,还有一个特性非常有利于使用类组件,那就是生命周期函数。此外还可能有一些其他的方法,用于进行某些运算,如果把函数式组件当成纯函数来说的话,那么副作用很显然就是除了渲染之外的其他作用,比如更改页面其他的属性,发送HTTP请求等等,对于一些组件来说这是必须的。
于是现在有了一个新的东西,可以在其中执行所有的副作用:
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
这里的副作用就是更新浏览器选项卡的标题,官方说法:如果你熟悉 React class
的生命周期函数,你可以把 useEffect Hook
看做 componentDidMount
,componentDidUpdate
和 componentWillUnmount
这三个函数的组合。我自己理解这句话的意思是:
- 副作用函数是在
React
更新DOM
之后才会执行。这样就无须在类中区分挂载和更新来编写冗余的代码。 - 对于
componentWillUnmount
中的操作,如果你的effect
参数函数又返回一个函数,React 将会在执行清除操作时调用它,这样useEffect
就同时也具备了componentWillUnmount
的功能。
Context Hook
在函数中引入context
这个用于在函数中获取context
:
const value = useContext(MyContext);
useContext
的函数的参数是固定的,就是context
对象本身。之后Provider
组件中的context
刷新的时候,这里的value
也会一同刷新。
本来使用context API
有特定的要求,类里必须存在两个东西:
- 类里需要定义一个静态变量:
static childContextTypes
getChildContext()
函数,用于设置context
内容
如此设置之后,在其他地方就可以使用this.context.attrName
来取用context
中的内容,或者在外层套一个Context.Provider
组件。
有了context
钩子之后,就可以省略上述的操作了,只需要在函数内之前定义一个context
对象。这里放一个通向官方例子的链接,待以后需要用到context
再回来看。
HOOK
规则
这里把官方的规则复制过来:
-
只在最顶层使用
Hook
不要在循环,条件或嵌套函数中调用Hook
, 确保总是在你的React
函数的最顶层调用他们。遵守这条规则,你就能确保Hook
在每一次渲染中都按照同样的顺序被调用。这让React
能够在多次的useState
和useEffect
调用之间保持hook
状态的正确。(如果你对此感到好奇,我们在下面会有更深入的解释。) -
只在
React
函数中调用Hook
。
不要在普通的JavaScript
函数中调用Hook
。你可以:
✅ 在React
的函数组件中调用Hook
✅ 在自定义Hook
中调用其他Hook
遵循此规则,确保组件的状态逻辑在代码中清晰可见。
好,现在有了若干新武器,Redux,Hook,剩下就是平时多使用一下。待到哪天需要使用自定义Hook再回来继续补完。总的来说,我觉得Hook
的出现主要是解决一些为了在组件间共享状态,不想使用多层传递,又嫌使用Redux
等额外工具太重型,于是就简单一些给函数组件添加上各种钩子来实现只有类组件才能实现的功能,目标还是为了让状态管理更加简便。