Hook 是 React 16.8 的新增特性。它可让你在不编写 class 的状况下使用 state 以及其余的 React 特性javascript
从官网的这句话中,咱们能够明确的知道,Hook
增长了函数式组件中state
的使用,在以前函数式组件是没法拥有本身的状态,只能经过props
以及context
来渲染本身的UI
,而在业务逻辑中,有些场景必需要使用到state
,那么咱们就只能将函数式组件定义为class
组件。而如今经过Hook
,咱们能够轻松的在函数式组件中维护咱们的状态,不须要更改成class
组件。html
React Hooks
要解决的问题是状态共享,这里的状态共享是指只共享状态逻辑复用,并非指数据之间的共享。咱们知道在React Hooks
以前,解决状态逻辑复用问题,咱们一般使用higher-order components
和render-props
,那么既然已经有了这两种解决方案,为何React
开发者还要引入React Hook
?对于higher-order components
和render-props
,React Hook
的优点在哪?java
咱们先来看一下React
官方给出的React Hook
的demo
ajax
import { useState } from 'React';
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
return (
<div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div>
);
}
复制代码
咱们再来看看不用React Hook
的话,如何实现npm
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
render() {
return (
<div> <p>You clicked {this.state.count} times</p> <button onClick={() => this.setState({ count: this.state.count + 1 })}> Click me </button> </div>
);
}
}
复制代码
能够看到,在React Hook
中,class Example
组件变成了函数式组件,可是这个函数式组件却拥有的本身的状态,同时还能够更新自身的状态。这一切都得益于useState
这个Hook
,useState
会返回一对值:当前状态和一个让你更新它的函数,你能够在事件处理函数中或其余一些地方调用这个函数。它相似 class
组件的 this.setState
,可是它不会把新的 state
和旧的 state
进行合并redux
React
复用状态逻辑的解决方案Hook
是另外一种复用状态逻辑的解决方案,React
开发者一直以来对状态逻辑的复用方案不断提出以及改进,从Mixin
到高阶组件到Render Props
到如今的Hook
,咱们先来简单了解一下之前的解决方案设计模式
Mixin
模式在React
最先期,提出了根据Mixin
模式来复用组件之间的逻辑。在Javascript
中,咱们能够将Mixin
继承看做是经过扩展收集功能的一种途径.咱们定义的每个新的对象都有一个原型,从中它能够继承更多的属性.原型能够从其余对象继承而来,可是更重要的是,可以为任意数量的对象定义属性.咱们能够利用这一事实来促进功能重用。数组
React
中的mixin
主要是用于在彻底不相关的两个组件中,有一套基本类似的功能,咱们就能够将其提取出来,经过mixin
的方式注入,从而实现代码的复用。例如,在不一样的组件中,组件须要每隔一段时间更新一次,咱们能够经过建立setInterval()
函数来实现这个功能,同时在组件销毁的时候,咱们须要卸载此函数。所以能够建立一个简单的 mixin
,提供一个简单的 setInterval()
函数,它会在组件被销毁时被自动清理。性能优化
var SetIntervalMixin = {
componentWillMount: function() {
this.intervals = [];
},
setInterval: function() {
this.intervals.push(setInterval.apply(null, arguments));
},
componentWillUnmount: function() {
this.intervals.forEach(clearInterval);
}
};
var createReactClass = require('create-React-class');
var TickTock = createReactClass({
mixins: [SetIntervalMixin], // 使用 mixin
getInitialState: function() {
return {seconds: 0};
},
componentDidMount: function() {
this.setInterval(this.tick, 1000); // 调用 mixin 上的方法
},
tick: function() {
this.setState({seconds: this.state.seconds + 1});
},
render: function() {
return (
<p>
React has been running for {this.state.seconds} seconds.
</p>
);
}
});
ReactDOM.render(
<TickTock />,
document.getElementById('example')
);
复制代码
mixin
的缺点mixin
可能会相互依赖,耦合性太强,致使后期维护成本太高mixin
中的命名可能会冲突,没法使用同一命名的mixin
mixin
即便开始很简单,它们会随着业务场景增多,时间的推移产生滚雪球式的复杂化具体缺点能够看此连接Mixins是一种祸害bash
由于mixin
的这些缺点存在,在React
中已经不建议使用mixin
模式来复用代码,React
全面推荐使用高阶组件来替代mixin
模式,同时ES6
自己是不包含任何 mixin
支持。所以,当你在 React
中使用 ES6 class
时,将不支持 mixins
。
高阶组件
(HOC)
是React
中用于复用组件逻辑的一种高级技巧。HOC
自身不是React API
的一部分,它是一种基于React
的组合特性而造成的设计模式
高级组件并非React
提供的API
,而是React
的一种运用技巧,高阶组件能够看作是装饰者模式(Decorator Pattern
)在React
的实现。装饰者模式: 动态将职责附加到对象上,若要扩展功能,装饰者提供了比继承更具弹性的代替方案.
具体而言,高阶组件是参数为组件,返回值为新组件的函数。
组件是将 props 转换为 UI,而高阶组件是将组件转换为另外一个组件
咱们能够经过高阶组件动态给其余组件增长日志打印功能,而不影响原先组件的功能
function logProps(WrappedComponent) {
return class extends React.Component {
componentWillReceiveProps(nextProps) {
console.log('Current props: ', this.props);
console.log('Next props: ', nextProps);
}
render() {
return <WrappedComponent {...this.props} />;
}
}
}
复制代码
术语 “Render Props” 是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术
具备 Render Props 的组件接受一个函数,该函数返回一个 React 元素并调用它而不是实现本身的渲染逻辑
如下咱们提供了一个带有prop
的<Mouse>
组件,它可以动态决定什么须要渲染,这样就能对<Mouse>
组件的逻辑以及状态复用,而不用改变它的渲染结构。
class Cat extends React.Component {
render() {
const mouse = this.props.mouse;
return (
<img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
);
}
}
class Mouse extends React.Component {
constructor(props) {
super(props);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.state = { x: 0, y: 0 };
}
handleMouseMove(event) {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
{this.props.render(this.state)}
</div>
);
}
}
class MouseTracker extends React.Component {
render() {
return (
<div>
<h1>移动鼠标!</h1>
<Mouse render={mouse => (
)}/>
</div>
);
}
}
复制代码
然而一般咱们说的Render Props
是由于模式才被称为 Render Props
,又不是由于必定要用render
对prop
进行命名。咱们也能够这样来表示
<Mouse>
{mouse => (
<Cat mouse={mouse} />
)}
</Mouse>
复制代码
React Hook
是官网提出的又一种全新的解决方案,在了解React Hook
以前,咱们先看一下React Hook
提出的动机
class
下面说说我对这三个动机的理解:
在组件之间复用状态逻辑很难,在以前,咱们经过高阶组件(Higher-Order Components
)和渲染属性(Render Propss
)来解决状态逻辑复用困难的问题。不少库都使用这些模式来复用状态逻辑,好比咱们经常使用redux
、React
Router
。高阶组件、渲染属性都是经过组合来一层层的嵌套共用组件,这会大大增长咱们代码的层级关系,致使层级的嵌套过于夸张。从React
的devtool
咱们能够清楚的看到,使用这两种模式致使的层级嵌套程度
复杂组件变得难以理解,在不断变化的业务需求中,组件逐渐会被状态逻辑以及反作用充斥,每一个生命周期经常会包含一些不相关的逻辑。咱们写代码一般都依据函数的单一原则,一个函数通常只处理一件事,但在生命周期钩子函数中一般会同时作不少事情。好比,在咱们须要在componentDidMount
中发起ajax
请求获取数据,同时有时候也会把事件绑定写在今生命周期中,甚至有时候须要在componentWillReceiveProps
中对数据进行跟componentDidMount
同样的处理。
相互关联且须要对照修改的代码被进行了拆分,而彻底不相关的代码却在同一个方法中组合在一块儿。如此很容易产生 bug,而且致使逻辑不一致。
难以理解的class,我的以为使用class
组件这种仍是能够的,只要了解了class
的this
指向绑定问题,其实上手的难度不大。你们要理解,这并非 React
特有的行为;这其实与 JavaScript 函数工做原理有关。因此只要了解好JS
函数工做原理,其实this
绑定都不是事。只是有时候为了保证this
的指向正确,咱们一般会写不少代码来绑定this
,若是忘记绑定的话,就有会各类bug
。绑定this
方法:
1.this.handleClick = this.handleClick.bind(this);
2.<button onClick={(e) => this.handleClick(e)}>
Click me
</button>
复制代码
因而为了解决以上问题,React Hook
就被提出来了
咱们回到刚刚的代码中,看一下如何在函数式组件中定义state
import React, { useState } from 'React';
const [count, setCount] = useState(0);
复制代码
useState
作了啥
咱们能够看到,在此函数中,咱们经过useState
定义了一个'state变量',它与 class
里面的 this.state
提供的功能彻底相同.至关于如下代码
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
复制代码
useState
参数
在代码中,咱们传入了0
做为useState
的参数,这个参数的数值会被当成count
初始值。固然此参数不限于传递数字以及字符串,能够传入一个对象当成初始的state
。若是state
须要储存多个变量的值,那么调用屡次useState
便可
useState
返回值
返回值为:当前 state
以及更新 state
的函数,这与 class
里面 this.state.count
和 this.setState
相似,惟一区别就是你须要成对的获取它们。看到[count, setCount]
很容易就能明白这是ES6的解构数组的写法。至关于如下代码
let _useState = useState(0);// 返回一个有两个元素的数组
let count = _useState[0];// 数组里的第一个值
let setCount = _useState[1];// 数组里的第二个值
复制代码
只须要使用变量便可
之前写法
<p>You clicked {this.state.count} times</p>
复制代码
如今写法
<p>You clicked {count} times</p>
复制代码
经过setCount
函数更新
之前写法
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
复制代码
如今写法
<button onClick={() => setCount(count + 1)}>
Click me
</button>
复制代码
这里setCount
接收的参数是修改过的新状态值
咱们能够在一个组件中屡次使用state Hook
来声明多个state
变量
function ExampleWithManyStates() {
// 声明多个 state 变量!
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
// ...
}
复制代码
React 假设当你屡次调用 useState
的时候,你能保证每次渲染时它们的调用顺序是不变的
为何React
要规定每次渲染它们时的调用顺序不变呢,这个是一个理解Hook
相当重要的问题
Hook
本质就是 JavaScript
函数,可是在使用它时须要遵循两条规则。而且React
要求强制执行这两条规则,否则就会出现异常的bug
Hook
不要在循环,条件或嵌套函数中调用 Hook, 确保老是在你的 React
函数的最顶层调用他们
React
函数中调用 Hook
不要在普通的 JavaScript 函数中调用 Hook
这两条规则出现的缘由是,咱们能够在单个组件中使用多个State Hook
或 Effect Hook
,React
靠的是 Hook
调用的顺序来知道哪一个 state
对应哪一个useState
function Form() {
const [name1, setName1] = useState('Arzh1');
const [name2, setName2] = useState('Arzh2');
const [name3, setName3] = useState('Arzh3');
// ...
}
// ------------
// 首次渲染
// ------------
useState('Arzh1') // 1. 使用 'Arzh1' 初始化变量名为 name1 的 state
useState('Arzh2') // 2. 使用 'Arzh2' 初始化变量名为 name2 的 state
useEffect('Arzh3') // 3. 使用 'Arzh3' 初始化变量名为 name3 的 state
// -------------
// 二次渲染
// -------------
useState('Arzh1') // 1. 读取变量名为 name1 的 state(参数被忽略)
useState('Arzh2') // 2. 读取变量名为 name2 的 state(参数被忽略)
useEffect('Arzh3') // 3. 读取变量名为 name3 的 state(参数被忽略)
复制代码
若是咱们违反React
的规则,使用条件渲染
if (name !== '') {
const [name2, setName2] = useState('Arzh2');
}
复制代码
假设第一次(name !== '')
为true
的时候,执行此Hook
,第二次渲染(name !== '')
为false
时,不执行此Hook
,那么Hook
的调用顺序就会发生变化,产生bug
useState('Arzh1') // 1. 读取变量名为 name1 的 state
//useState('Arzh2') // 2. Hook被忽略
useEffect('Arzh3') // 3. 读取变量名为 name2(以前为name3) 的 state
复制代码
React
不知道第二个 useState
的 Hook
应该返回什么。React
会觉得在该组件中第二个 Hook
的调用像上次的渲染同样,对应的是 arzh2
的 useState
,但并不是如此。因此这就是为何React
强制要求Hook
使用必须遵循这两个规则,同时咱们可使用 eslint-plugin-React-Hooks
来强制约束
咱们在上面的代码中增长Effect Hook
的使用,在函数式组件中增长反作用,修改网页的标题
useEffect(() => {
document.title = `You clicked ${count} times`;
});
复制代码
若是你熟悉 React class 的生命周期函数,你能够把
useEffect
Hook 看作componentDidMount
,componentDidUpdate
和componentWillUnmount
这三个函数的组合。
也就是咱们彻底能够经过useEffect
来替代这三个生命钩子函数
咱们来了解一下一般须要反作用的场景,好比发送请求,手动变动dom
,记录日志等。一般咱们都会在第一次dom
渲染完成以及后续dom
从新更新时,去调用咱们的反作用操做。咱们能够看一下之前生命周期的实现
componentDidMount() {
document.title = `You clicked ${this.state.count} times`;
}
componentDidUpdate() {
document.title = `You clicked ${this.state.count} times`;
}
复制代码
这也就是咱们上面提到的React Hook
动机的第二个问题来源之一,须要在第一次渲染以及后续的渲染中调用相同的代码
Effect
在默认状况下,会在第一次渲染以后和每次更新以后都会执行,这也就让咱们不须要再去考虑是componentDidMount
仍是componentDidUpdate
时执行,只须要明白Effect在组件渲染后执行便可
有时候对于一些反作用,咱们是须要去清除的,好比咱们有个需求须要轮询向服务器请求最新状态,那么咱们就须要在卸载的时候,清理掉轮询的操做。
componentDidMount() {
this.pollingNewStatus()
}
componentWillUnmount() {
this.unPollingNewStatus()
}
复制代码
咱们可使用Effect
来清除这些反作用,只须要在Effect
中返回一个函数便可
useEffect(() => {
pollingNewStatus()
//告诉React在每次渲染以前都先执行cleanup()
return function cleanup() {
unPollingNewStatus()
};
});
复制代码
有个明显的区别在于useEffect
实际上是每次渲染以前都会去执行cleanup()
,而componentWillUnmount
只会执行一次。
useEffect
实际上是每次更新都会执行,在某些状况下会致使性能问题。那么咱们能够经过跳过 Effect
进行性能优化。在class
组件中,咱们能够经过在 componentDidUpdate
中添加对 prevProps
或 prevState
的比较逻辑解决
componentDidUpdate(prevProps, prevState) {
if (prevState.count !== this.state.count) {
document.title = `You clicked ${this.state.count} times`;
}
}
复制代码
在Effect
中,咱们能够经过增长Effect
的第二个参数便可,若是没有变化,则跳过更新
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新
复制代码
因为篇幅缘由,就再也不此展开了,有兴趣能够自行官网查看