1.什么是 React Hooks
官方介绍:Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
2.React Hooks的优点是什么
- 拥有了hooks,你再也不需要写Class了,你的所有组件都将是Function。
- 拥有了Hooks,生命周期钩子函数可以先丢一边了。
- 既然Class都丢掉了,哪里还有this?你的人生第一次不再需要面对this。
3.一个最简单的Hooks
以下是一个原本应该使用class的简单应用,利用Hooks改造的结果。
我们再来看一下使用hooks后的版本:
import { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
return (
You clicked {count} times
);
}
可以看到, Example变成了一个函数,但这个函数却有自己的状态(count),同时它还可以更新自己的状态(setCount)。这个函数之所以这么了不得,就是因为它注入了一个hook--useState,就是这个hook让我们的函数变成了一个有状态的函数。
除了useState这个hook外,还有很多别的hook,比如useEffect提供了类似于componentDidMount等生命周期钩子的功能,useContext提供了上下文context的功能等等。
Hooks本质上就是一类特殊的函数,它们可以为你的函数型组件(function component)注入一些特殊的功能。
4.React为什么要搞一个Hooks?
1.想要复用一个有状态的组件太麻烦了!
引例:如果我们想要复用一个组件,面对成篇的 class 其中 的this 的指向 是一个十分让人头疼的 问题。
官方提供的解决方法是:
渲染属性(Render Props)
高阶组件(Higher-Order Components)
渲染属性---指的是使用一个值为函数的prop来传递需要动态渲染的nodes或组件。(利用属性或者插槽的方式都可以)
高阶组件---这个概念就更好理解了,说白了就是一个函数接受一个组件作为参数,经过一系列加工后,最后返回一个新的组件。
----以上这两种模式看上去都挺不错的,很多库也运用了这种模式,比如我们常用的React Router。
----但我们仔细看这两种模式,会发现它们会增加我们代码的层级关系。最直观的体现,打开devtool看看你的组件层级嵌套是不是很夸张吧。
----这时候再回过头看我们上一节给出的hooks例子,是不是简洁多了,没有多余的层级嵌套。把各种想要的功能写成一个一个可复用的自定义hook,当你的组件想用什么功能时,直接在组件里调用这个hook即可。
2.生命周期钩子函数里的逻辑太乱了
我们通常希望一个函数只做一件事情,但我们的生命周期钩子函数里通常同时做了很多事情。比如我们需要在componentDidMount中发起ajax请求获取数据,绑定一些事件监听等等。同时,有时候我们还需要在componentDidUpdate做一遍同样的事情。当项目变复杂后,这一块的代码也变得不那么直观。
3.classes真的太让人困惑了
我们用class来创建react组件时,还有一件很麻烦的事情,就是this的指向问题。为了保证this的指向正确,我们要经常写这样的代码:this.handleClick = this.handleClick.bind(this),或者是这样的代码:
还有一件让我很苦恼的事情。我在之前的react系列文章当中曾经说过,尽可能把你的组件写成无状态组件的形式,因为它们更方便复用,可独立测试。然而很多时候,我们用function写了一个简洁完美的无状态组件,后来因为需求变动这个组件必须得有自己的state,我们又得很麻烦的把function改成class。
在这样的背景下,Hooks便横空出世了!
5.什么是State Hooks?
回到一开始我们用的例子,我们分解来看到底state hooks做了什么:
import { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
return (
You clicked {count} times
);
声明一个状态变量
import { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
读取状态值
You clicked {count} times
更新状态
一个至关重要的问题
这里我们就发现了问题,通常来说我们在一个函数中声明的变量,当函数运行完成后,这个变量也就销毁了(这里我们先不考虑闭包等情况)
假如一个组件有多个状态值怎么办?
react是怎么保证多个useState的相互独立的?
答案是,react是根据useState出现的顺序来定的。
鉴于此,react规定我们必须把hooks写在函数的最外层,不能写在ifelse等条件语句当中,来确保hooks的执行顺序一致。
6.什么是Effect Hooks?
我们在上一节的例子中增加一个新功能:
import { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// 类似于componentDidMount 和 componentDidUpdate:
useEffect(() => {
// 更新文档的标题
document.title = `You clicked ${count} times`;
});
return (
You clicked {count} times
);
}
我们对比着看一下,如果没有hooks,我们会怎么写?:
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
componentDidMount() {
document.title = `You clicked ${this.state.count} times`;
}
componentDidUpdate() {
document.title = `You clicked ${this.state.count} times`;
}
render() {
return (
You clicked {this.state.count} times
);
}
}
useEffect做了什么?
useEffect怎么解绑一些副作用
为什么要让副作用函数每次组件更新都执行一遍?
很清除,我们在componentDidMount注册,再在componentWillUnmount清除注册。但假如这时候props.friend.id变了怎么办?我们不得不再添加一个componentDidUpdate来处理这种情况
看到了吗?很繁琐,而我们但useEffect则没这个问题,因为它在每次组件更新后都会重新执行一遍。所以代码的执行顺序是这样的:
1.页面首次渲染
2.替friend.id=1的朋友注册
3.突然friend.id变成了2
4.页面重新渲染
5.清除friend.id=1的绑定
6.替friend.id=2的朋友注册
...
怎么跳过一些不必要的副作用函数
按照上一节的思路,每次重新渲染都要执行一遍这些副作用函数,显然是不经济的。怎么跳过一些不必要的计算呢?我们只需要给useEffect传第二个参数即可。用第二个参数来告诉react只有当这个参数的值发生改变时,才执行我们传的副作用函数(第一个参数)。
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // 只有当count的值发生变化时,才会重新执行`document.title`这一句
当我们第二个参数传一个空数组[]时,其实就相当于只在首次渲染的时候执行。也就是componentDidMount加componentWillUnmount的模式。不过这种用法可能带来bug,少用。
还有哪些自带的Effect Hooks?
未完待续。。。。。。