React Hooks是React 16.7.0-alpha版本推出的新特性,目的是解决React的状态共享问题。称之为状态共享可能描述的并非很恰当,称为状态逻辑复用可能会更恰当,由于React Hooks只共享数据处理逻辑,并不会共享数据自己。 在React应用开发中,状态管理是组件开发必不可少的内容。之前,为了对状态进行管理,最一般的作法是使用类组件或者直接使用redux等状态管理框架。例如:react
class Hook 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 Hooks提供的State Hook来处理状态,针对那些已经存在的类组件,也可使用State Hook很好地进行重构。例如:ios
import React, { useState } from 'react';
function Hook() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
复制代码
从上面的示例能够发现,Example变成了一个函数组件,此函数组件有本身的状态,而且还能够更新本身的状态。之因此能够如此操做,是由于使用了useState,useState是react自带的一个hook函数,它的做用就是用来声明状态变量。编程
一直以来,React就在致力于解决一个问题,即状态组件的复用问题。在React应用开发中,咱们都是经过组件和自上而下传递的数据流来将大型的视图拆分红独立的可复用组件。可是在实际项目开发中,复用一个带有业务逻辑的组件依然是一个难题。 众所周知,React提供了两种建立组件的方式,即函数组件和类组件。函数组件是一个普通的JavaScript函数,接受props对象并返回React元素,函数组件更符合React数据驱动视图的开发思想。不过,函数组件一直以来都由于缺少类组件诸如状态、生命周期等种种特性,也由于这些缘由函数组件得不到开发者的青睐,而Hooks的出现就是让函数式组件拥有类组件的特性。 为了让函数组件拥有类组件的诸如状态、生命周期等特性,React 提供了3个核心的api,即State Hooks、Effect Hooks和Custom Hooks。 useState就是React提供最基础、最经常使用的Hook,主要用来定义和管理本地状态。例如,下面是使用useState实现一个最简单的计数器,代码以下:redux
import React, { useState } from 'react'
function App() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={()=> setCount(count + 1)}>+</button>
<span>{count}</span>
<button onClick={() => setCount((count) => count - 1)}>-</button>
</div>
);
}
export default App;
复制代码
在上面的示例中,使用useState来定义一个状态,与类组件的状态不一样,函数组件的状态能够是对象也能够是基础类型值。useState返回的是一个数组,数组的第一个对象表示当前状态的值,第二个对象表示用于更改状态的函数,相似于类组件的setState。 函数组件中若是存在多个状态,既能够经过一个useState声明对象类型的状态,也能够经过useState屡次声明状态。例如:axios
//声明对象类型状态
const [count, setCount] = useState({
count1: 0,
count2: 0
});
//屡次声明
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
复制代码
能够发现,相比于声明对象类型状态,屡次声明状态的方式更加方便,主要是由于更新函数是采用的替换的方式而不是合并的方式。 不过,若是要在函数组件中处理多层嵌套数据逻辑时,使用useState就显得力不从心了。值得庆幸的是,开发者可使用React提供的useReducer来处理此类问题。例如:api
import React, {useReducer} from 'react'
const reducer = function (state, action) {
switch (action.type) {
case "increment":
return {count: state.count + 1};
case "decrement":
return {count: state.count - 1};
default:
return {count: state.count}
}
};
function Example() {
const [state, dispatch] = useReducer(reducer, {count: 0});
const {count} = state;
return (
<div>
<button onClick={() => dispatch({type: "increment"})}>+</button>
<span>{count}</span>
<button onClick={() => dispatch({type: "decrement"})}>-</button>
</div>
);
}
export default Example;
复制代码
能够发现,useReducer接受reducer函数和默认值两个参数,并返回当前状态state和dispatch函数的数组,使用方式与Redux基本一致,不一样之处在于,Redux的默认值是经过给reducer函数设置默认参数的方式给定的。 useReducer之因此没有采用Redux的方式设置默认值,是由于React认为状态的的默认值多是来自于函数组件的props。例如:数组
function Example({initialState = 0}) {
const [state, dispatch] = useReducer(reducer, { count: initialState });
...
}
复制代码
解决了函数组件中内部状态的问题,接下来亟待解决的就是函数组件中生命周期函数的问题。在React的函数式编程思想中,生命周期函数是沟通函数式和命令式的桥梁,根据生命周期函数开发者能够执行相关的操做,如网络请求和操做DOM。浏览器
import React, {useState, useEffect} from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('componentDidMount...')
console.log('componentDidUpdate...')
return () => {
console.log('componentWillUnmount...')
}
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
export default Example;
复制代码
执行上面的代码,而后点击按钮执行加法操做时,生命周期函数调用状况下图所示。 bash
众所周知,要在类组件之间共享一些状态逻辑是很是麻烦的,常规的作法都是经过高阶组件或者是函数的属性来解决。不过,新版的React容许开发者建立自定义Hook来封装共享状态逻辑,且不须要向组件树中增长新的组件。 所谓的自定义Hook,其实就是指函数名以use开头并调用其余Hook的函数,自定义Hook的每一个状态都是彻底独立的。例如,下面是使用axios实现网络请求的示例,代码以下:网络
import axios from 'axios'
export const useAxios = (url, dependencies) => {
const [isLoading, setIsLoading] = useState(false);
const [response, setResponse] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
setIsLoading(true);
axios.get(url).then((res) => {
setIsLoading(false);
setResponse(res);
}).catch((err) => {
setIsLoading(false);
setError(err);
});
}, dependencies);
return [isLoading, response, error];
};
复制代码
如上所示,就是使用axios开发请求数据的自定义Hook。使用方法和系统提供的Hook相似,直接调用便可。例如:
function Example() {
let url = 'http://api.douban.com/v2/movie/in_theaters';
const [isLoading, response, error] = useAxios(url, []);
return (
<div>
{isLoading ? <div>loading...</div> :
(error ? <div> There is an error happened </div> : <div> Success, {response} </div>)}
</div>
)
}
export default Example;
复制代码
能够发现,相比于函数属性和高阶组件等方式,自定义Hook则更加的简洁易读,不只于此,自定义Hook也不会引发之组件嵌套地狱问题。 虽然React的Hooks有着诸多的优点。不过,在使用Hooks的过程当中,须要注意如下两点: • 不要在循环、条件或嵌套函数中使用Hook,而且只能在React函数的顶层使用Hook。之因此要这么作,是由于React须要利用调用顺序来正确更新相应的状态,以及调用相应的生命周期函数函数。一旦在循环或条件分支语句中调用Hook,就容易致使调用顺序的不一致性,从而产生难以预料到的后果。 • 只能在React函数式组件或自定义Hook中使用Hook。 同时,为了不在开发中形成一些低级的错误,能够安装一个eslint插件,安装命令以下:
yarn add eslint-plugin-react-hooks --dev
复制代码
而后,在eslint的配置文件中添加以下一些配置。
{
"plugins": [
// ...
"react-hooks"
],
"rules": {
// ...
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn"
}
}
复制代码
借助于React提供的Hooks API,函数组件能够实现绝大部分的类组件功能,而且Hooks在共享状态逻辑、提升组件复用性上也具备必定的优点。能够预见的是,Hooks将是React将来发展的重要方向。