在开始之前,先看一张图:
为什么要推出 React Hooks?
React Hooks 的设计目的,就是加强版函数组件,完全不使用"类",就能写出一个全功能的组件。
不准确的总结一下,就是:React 团队希望开发者们少用类组件,多用函数组件。
这里我们就有一个疑问了:类组件有啥不好?函数组件有啥好?
类组件的缺点
笔者认为,组件类有这么几个问题:
- 积重难返:大型组件很难拆分和重构,也很难测试。
- 逻辑分散:业务逻辑分散在组件的各个方法之中,导致重复逻辑或关联逻辑。
- 难以复用:为了在类组件的基础上实现复用,引入了复杂的编程模式,比如 render props 和高阶组件。
React 团队希望:
组件不要变成复杂的容器,最好只是数据流的管道。开发者根据需要,组合管道即可。
组件的最佳写法应该是函数,而不是类。
从实现一个组件内请求说起……
下面,我将尝试通过实现一个组件内请求,来尝试说明类组件和函数组件的不同。
在类组件中,我们如果要实现发请求,得这么做:
import React from 'react';
class Component extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: false,
info: null,
};
}
componentDidMount() {
this.fetchInfo();
}
fetchInfo = async () => {
this.setState({ loading: true });
const info = await getInfo();
this.setState({
info,
loading: false,
});
}
render() {
const { info } = this.state;
return <div>{info}</div>;
}
}
export default Component;
问题1:逻辑分散
简单的一个组件内请求,业务逻辑分散在了组件的 4 个方法里面。
- constructor
- state 初始化
- componentDidMount
- 执行请求方法
- fetchInfo
- 发起请求,处理数据
- render
- 根据状态返回内容
当组件逐渐搭起来之后,开发者一旦疏忽,就很容易导致重复逻辑或关联逻辑。
问题2:难以拆分和重构,也很难测试
在上述代码中,请求的逻辑是跟组件的生命周期强耦合的,代码放在了 3 个 react 生命周期钩子函数中。
当组件逐渐大起来之后,一个 componentDidMount 可能都数十甚至上百行,想要解耦、拆分、重构,谈何容易呀!
问题3:引入新功能麻烦,对开发者不够简单
此时,我们想要加入一个 loading 状态,那么必须:
- 在 constructor 里,this.state 中声明一个 loading
- 在 fetchInfo 中加入对 loading 的状态处理
- 在 render 中对 loading 做特殊判断
那么,我如果还想做「解析参数、依赖处理、全局配置、请求数据逻辑、回调处理、循环请求、缓存处理」等功能呢?
靓仔语塞。
问题4:难以复用
上面这些代码都是与组件的生命周期强相关的,难以将其抽象出来。为了实现抽象的目的,我们只能借助一些复杂的编程模式,如渲染属性(render props)和高阶组件(HOC)。
那如果用 react hooks 要怎么操作?
简单版:
import { useState, useEffect } from 'react';
const Component = () => {
const [ loading, setLoading ] = useState(false);
const [ info, setInfo ] = useState('');
useEffect(() => {
setLoading(true);
const info = await getInfo();
setLoading(false);
setInfo(info);
}, []);
if (loading) return <div>加载中……</div>;
return <div>{info}</div>
}
export default Component;
但是,别忘了,Hooks 最重要的能力就是逻辑复用!这些逻辑我们完全可以封装起来!
进阶版:
import { useState, useEffect } from 'react';
function useInfo() {
const [ loading, setLoading ] = useState(false);
const [ info, setInfo ] = useState('');
useEffect(() => {
setLoading(true);
const info = await getInfo();
setLoading(false);
setInfo(info);
}, []);
return { loading, info };
}
const Component = () => {
const { info, loading } = useInfo();
if (loading) return <div>加载中……</div>;
return <div>{info}</div>
}
export default Component;
我的天,请求方法居然被抽象出来了 !它(useInfo)可以当做一个通用逻辑被复用了!
所有请求相关的处理逻辑,都放在了 userInfo 这里。
它的好处一目了然:
学习成本低,一眼就知道你这个代码想干嘛
业务逻辑集中,所有东西都在 useInfo 里面
-
10 分钟快速入门:React Hooks
为什么要推出 React Hooks?
React Hooks 的设计目的,就是加强版函数组件,完全不使用"类",就能写出一个全功能的组件。
不准确的总结一下,就是:React 团队希望开发者们少用类组件,多用函数组件。
这里我们就有一个疑问了:类组件有啥不好?函数组件有啥好?
类组件的缺点
笔者认为,组件类有这么几个问题:
- 积重难返:大型组件很难拆分和重构,也很难测试。
- 逻辑分散:业务逻辑分散在组件的各个方法之中,导致重复逻辑或关联逻辑。
- 难以复用:为了在类组件的基础上实现复用,引入了复杂的编程模式,比如 render props 和高阶组件。
React 团队希望:
组件不要变成复杂的容器,最好只是数据流的管道。开发者根据需要,组合管道即可。
组件的最佳写法应该是函数,而不是类。
从实现一个组件内请求说起……
下面,我将尝试通过实现一个组件内请求,来尝试说明类组件和函数组件的不同。
在类组件中,我们如果要实现发请求,得这么做:
import React from 'react'; class Component extends React.Component { constructor(props) { super(props); this.state = { loading: false, info: null, }; } componentDidMount() { this.fetchInfo(); } fetchInfo = async () => { this.setState({ loading: true }); const info = await getInfo(); this.setState({ info, loading: false, }); } render() { const { info } = this.state; return <div>{info}</div>; } } export default Component;
问题1:逻辑分散
简单的一个组件内请求,业务逻辑分散在了组件的 4 个方法里面。
- constructor
- state 初始化
- componentDidMount
- 执行请求方法
- fetchInfo
- 发起请求,处理数据
- render
- 根据状态返回内容
当组件逐渐搭起来之后,开发者一旦疏忽,就很容易导致重复逻辑或关联逻辑。
问题2:难以拆分和重构,也很难测试
在上述代码中,请求的逻辑是跟组件的生命周期强耦合的,代码放在了 3 个 react 生命周期钩子函数中。
当组件逐渐大起来之后,一个 componentDidMount 可能都数十甚至上百行,想要解耦、拆分、重构,谈何容易呀!
问题3:引入新功能麻烦,对开发者不够简单
此时,我们想要加入一个 loading 状态,那么必须:
- 在 constructor 里,this.state 中声明一个 loading
- 在 fetchInfo 中加入对 loading 的状态处理
- 在 render 中对 loading 做特殊判断
那么,我如果还想做「解析参数、依赖处理、全局配置、请求数据逻辑、回调处理、循环请求、缓存处理」等功能呢?
靓仔语塞。
问题4:难以复用
上面这些代码都是与组件的生命周期强相关的,难以将其抽象出来。为了实现抽象的目的,我们只能借助一些复杂的编程模式,如渲染属性(render props)和高阶组件(HOC)。
那如果用 react hooks 要怎么操作?
简单版:
import { useState, useEffect } from 'react';
const Component = () => {
const [ loading, setLoading ] = useState(false);
const [ info, setInfo ] = useState('');
useEffect(() => {
setLoading(true);
const info = await getInfo();
setLoading(false);
setInfo(info);
}, []);
if (loading) return <div>加载中……</div>;
return <div>{info}</div>
}
export default Component;
但是,别忘了,Hooks 最重要的能力就是逻辑复用!这些逻辑我们完全可以封装起来!
进阶版:
import { useState, useEffect } from 'react';
function useInfo() {
const [ loading, setLoading ] = useState(false);
const [ info, setInfo ] = useState('');
useEffect(() => {
setLoading(true);
const info = await getInfo();
setLoading(false);
setInfo(info);
}, []);
return { loading, info };
}
const Component = () => {
const { info, loading } = useInfo();
if (loading) return <div>加载中……</div>;
return <div>{info}</div>
}
export default Component;
我的天,请求方法居然被抽象出来了 !它(useInfo)可以当做一个通用逻辑被复用了!
所有请求相关的处理逻辑,都放在了 userInfo 这里。
它的好处一目了然:
- 学习成本低,一眼就知道你这个代码想干嘛
- 业务逻辑集中,所有东西都在 useInfo 里面
- 可以复用,直接把 useInfo 拿出去,就能到处跑
代码组织复杂度对比:类组件 VS 函数组件
React Hooks 有哪几个 API?分别都是干什么用的?
React Hooks 的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩”进来。
函数组件的一些特性
在了解 API 之前,我想先跟你说说函数组件的一些特性,方便你理解:
- 每次 state、props 改变,都会重新执行一遍;
- 函数组件中的 useXXX 只会创建一次;
- 函数跑完之后,返回了新的 jsx 之后,才会执行 useEffect。useEffect 相当于 componentDidXXX。
当你不能理解下面的 API 时,回过头来看看这函数组件的这几个特性,能帮助你更好的理解它们。
useState():状态钩子
useState()用于为函数组件引入状态(state)。纯函数不能有状态,所以把状态放在钩子里面。
import React, { 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>
);
}
上面代码等同于:
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>
);
}
}
useContext():共享状态钩子
如果需要在组件之间共享状态,可以使用useContext()。
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
const ThemeContext = React.createContext(themes.light);
function App() {
return (
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme.background, color: theme.foreground }}>
I am styled by theme context!
</button>
);
}
useReducer():action 钩子
useReducers() 钩子用来引入类似 Redux 中的 Reducer 功能(不完全版)。
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
useEffect():副作用钩子
useEffect()用来引入具有副作用的操作,最常见的就是向服务器请求数据。以前,放在componentDidMount里面的代码,现在可以放在useEffect()。
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>
);
}
上面的代码等同于:
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 (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}
React Hooks 为什么这么香?
在我看来,React Hooks 必然是 React 开发的大势所趋,原因就在于:
React Hooks 提供了开发者对一个功能做精做细的能力。大量出现优秀的功能库,使用者们只需要调用一句 useXXX,就可以解决一个大问题。
举个栗子:
在社区里面,有一个非常棒的基于 react hooks 开发的请求库:SWR。在这个库里面,他解决了请求方方面面的问题:解析参数、依赖处理、全局配置、请求数据逻辑、回调处理、循环请求、缓存处理……
想象一下,这样的场景,在如果要在类组件里面实现,而且希望你可以复用,实现起来该多难!
但 react hooks 出现之后,一扫阴霾。我们只需要基于 react hooks 的各个 API,实现了所需功能之后,将它都封装在一个 useSWR
里面。
对于开发者,只需要一句话:const { data, error } = useSWR('/api/user', fetcher)
就能用上所有功能了!简直不要太方便啊!逻辑集中且明确,一次编写多处复用,香!
另外,社区中还有很多优秀的库,这么一些合集:
react-query - Hooks for fetching
他们能大大的提高开发者的效率,非常值得大家去了解和使用。可以复用,直接把 useInfo 拿出去,就能到处跑
代码组织复杂度对比:类组件 VS 函数组件
[图片上传失败...(image-540c5f-1582016358590)]
React Hooks 有哪几个 API?分别都是干什么用的?
React Hooks 的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩”进来。
函数组件的一些特性
在了解 API 之前,我想先跟你说说函数组件的一些特性,方便你理解:
- 每次 state、props 改变,都会重新执行一遍;
- 函数组件中的 useXXX 只会创建一次;
- 函数跑完之后,返回了新的 jsx 之后,才会执行 useEffect。useEffect 相当于 componentDidXXX。
当你不能理解下面的 API 时,回过头来看看这函数组件的这几个特性,能帮助你更好的理解它们。
useState():状态钩子
useState()用于为函数组件引入状态(state)。纯函数不能有状态,所以把状态放在钩子里面。
import React, { 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>
);
}
上面代码等同于:
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>
);
}
}
useContext():共享状态钩子
如果需要在组件之间共享状态,可以使用useContext()。
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
const ThemeContext = React.createContext(themes.light);
function App() {
return (
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme.background, color: theme.foreground }}>
I am styled by theme context!
</button>
);
}
useReducer():action 钩子
useReducers() 钩子用来引入类似 Redux 中的 Reducer 功能(不完全版)。
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
useEffect():副作用钩子
useEffect()用来引入具有副作用的操作,最常见的就是向服务器请求数据。以前,放在componentDidMount里面的代码,现在可以放在useEffect()。
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>
);
}
上面的代码等同于:
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 (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}
React Hooks 为什么这么香?
在我看来,React Hooks 必然是 React 开发的大势所趋,原因就在于:
React Hooks 提供了开发者对一个功能做精做细的能力。大量出现优秀的功能库,使用者们只需要调用一句 useXXX,就可以解决一个大问题。
举个栗子:
在社区里面,有一个非常棒的基于 react hooks 开发的请求库:SWR。在这个库里面,他解决了请求方方面面的问题:解析参数、依赖处理、全局配置、请求数据逻辑、回调处理、循环请求、缓存处理……
想象一下,这样的场景,在如果要在类组件里面实现,而且希望你可以复用,实现起来该多难!
但 react hooks 出现之后,一扫阴霾。我们只需要基于 react hooks 的各个 API,实现了所需功能之后,将它都封装在一个 useSWR
里面。
对于开发者,只需要一句话:const { data, error } = useSWR('/api/user', fetcher)
就能用上所有功能了!简直不要太方便啊!逻辑集中且明确,一次编写多处复用,香!
另外,社区中还有很多优秀的库,这么一些合集:
react-query - Hooks for fetching
他们能大大的提高开发者的效率,非常值得大家去了解和使用。