今天尝试下React Hooks的学习.

什么是React Hooks?

React Hooks和传统的开发模式有何不同?

使用React Hooks有哪些好处?

什么是React Hooks?

官方解释:Hook 是 React 16.8 的新增特性,它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性,如:保存状态、保存上下文、保存引用等.Hook 本质就是 JavaScript 函数.说白了就是对于函数组件的加强,增加了对于函数组件的一些特性,像class组件那样使用,是一套全新的API或者设计思想.

它和传统开发模式有何异同?

class和react-hooks代码量对比     官方推荐使用钩子(函数),而不是类。因为钩子更简洁,代码量少,用起来比较"轻",而类比较"重"。而且,钩子是函数,更符合 React 函数式的本质。 类(class)是数据和逻辑的封装。 也就是说,组件的状态和操作方法是封装在一起的。如果选择了类的写法,就应该把相关的数据和操作,都写在同一个 class 里面。
    Hook 这个单词的意思是"钩子"。React Hooks 的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来。 React Hooks 就是那些钩子。你需要什么功能,就使用什么钩子。React 默认提供了一些常用钩子,你也可以封装自己的钩子。所有的钩子都是为函数引入外部功能,所以 React 约定,钩子一律使用use前缀命名,便于识别。你要使用 xxx 功能,钩子就命名为 usexxx。
    Redux 的作者 Dan Abramov 总结了组件类的几个缺点。
1
2
3
4
5

大型组件很难拆分和重构,也很难测试。
业务逻辑分散在组件的各个方法之中,导致重复逻辑或关联逻辑。
组件类引入了复杂的编程模式,比如 render props 和高阶组件。

    吹得都上天了,其实 御姐小萝莉,各有所爱,各有所长.据官方资料来说:
1
2
3
4
5
6
7

完全可选的。 你无需重写任何已有代码就可以在一些组件中尝试 Hook。但是如果你不想,你不必现在就去学习或使用 Hook。
100% 向后兼容的。 Hook 不包含任何破坏性改动。
现在可用。 Hook 已发布于 v16.8.0。
没有计划从 React 中移除 class。
Hook 不会影响你对 React 概念的理解。 恰恰相反,Hook 为已知的 React 概念提供了更直接的 API:props, state,context,refs 以及生命周期。

实操一把

react-hooks总览 准备工作:
1、react版本需 16.8.0以上.

2、使用 create-react-appnpmyarn等方式新建一个最基本的项目即可,如:npx create-react-app my-app

传统模式-->Example-class.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import  React,{Component} from 'react'
// class的基本演示
class ExampleClass extends Component {
constructor(props){
super(props);
this.state = {count:0}
}
// 组件第一次执行
componentDidMount(){
console.log('ExampleClass执行了')
}
render(){
return (
<div>
<p>class简单使用</p>
<p>{this.state.count}</p>
<button onClick={()=> {this.setState({count:this.state.count+1})}}>+1</button>
<button onClick={()=> {this.setState({count:this.state.count-1})}}>-1</button>
</div>
)
}

}
export default ExampleClass;

useState事例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React, { useState,useEffect } from 'react'

// useState 简单使用

function Example1() {
const [count, setCount] = useState(0);

// 此处的useEffect相当于componentDidMount 和 componentDidUpdate
useEffect(()=>{
console.log('Example1下 useEffect执行了');
})

return <div>
<p>useState的简单使用</p>
<p> {count}</p>
<button onClick={() => { setCount(count + 1) }}>+1</button>
<button onClick={() => { setCount(count - 1) }}>-1</button>
</div>
}

export default Example1;

useEffect:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import React, { useState ,useEffect} from 'react'

function Example2() {
const [count, setCount] = useState(0);
const [color, setColor] = useState('red');

// 此处的useEffect相当于componentDidMount 和 color的componentDidUpdate
useEffect(()=>{
console.log('------切换color--------');
},[color])

// 此处的useEffect相当于componentDidMount 和 count的componentDidUpdate
useEffect(()=>{
console.log('点击count');
},[count])

function changeColor(){
if(color === 'yellow'){
setColor('red');
}else{
setColor('yellow');
}
}

return <div style={{background:color}}>
<p>useEffect简单使用</p>
<p> {count}</p>
<button onClick={() => { setCount(count + 1) }}>+1</button>
<button onClick={() => { setCount(count - 1) }}>-1</button>
<button onClick={() => changeColor()}>切换颜色</button>
</div>

}

export default Example2;

useEffect代替componentWillUnmount使用(需要安装react-router-dom):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import React, { useEffect } from 'react'
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';


function Index1() {
useEffect(() => {
console.log('组件1componentDidMount ');
return () => {
console.log('组件1componentWillUnmount');
}
})
return <div>
<h2>组件1</h2>
</div>
}

function Index2() {
useEffect(() => {
console.log('组件2componentDidMount ');
return () => {
console.log('组件2componentWillUnmount');
}
})
return <div>
<h2>组件2</h2>
</div>
}

function Example3() {

return <div>
<p>useEffect代替componentWillUnmount使用</p>
<Router>
<ul>
<li><Link to="/">组件1</Link></li>
<li><Link to="/two/">组件2</Link></li>
</ul>
<Route path="/" exact component={Index1} />
<Route path="/two/" component={Index2} />
</Router>

</div>

}

export default Example3;

useContext的简单使用:父子之间传递数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import React, { useState,createContext,useContext } from 'react'

// useState 简单使用

function Example4() {
const [count, setCount] = useState(0);
const CountContext = createContext();

function Child(){
let count = useContext(CountContext);
return (
<h1>我是子组件{count}</h1>
)
}

return <div>
<p>useContext的简单使用:父子之间传递数据</p>
<p>我是父组件: {count}</p>
<button onClick={() => { setCount(count + 1) }}>+1</button>
<button onClick={() => { setCount(count - 1) }}>-1</button>
<CountContext.Provider value={count}>
<Child />
</CountContext.Provider>
</div>
}

export default Example4;

useRef使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import React, { useState,useRef } from 'react'

// useRef 简单使用

function Example5() {
const [inputVal,setInputVal] = useState('')
const ref = useRef();
function inputAction (){
console.log(ref.current.value);
setInputVal(ref.current.value);
}
return <div>
<p>useRef使用:{inputVal}</p>
<input onChange={inputAction} ref={ref} />
</div>
}

export default Example5;

useReducer的简单使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

import React, { useReducer } from 'react';

const initialState = 0;
const reducer = (state, action) => {
switch (action) {
case 'increment': return state + 1;
case 'decrement': return state - 1;
case 'reset': return 0;
default: throw new Error('Unexpected action');
}
};

const Example6 = () => {
const [count, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>useReducer的简单使用</p>
{count}
<button onClick={() => dispatch('increment')}>+1</button>
<button onClick={() => dispatch('decrement')}>-1</button>
<button onClick={() => dispatch('reset')}>reset</button>
</div>
);
};

export default Example6;

useMemo提升性能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import React, { useState, useMemo } from 'react'

// useState 简单使用
function Example7() {
const [name, setName] = useState('name');
const [content, setContent] = useState('content');

return (
<div>
<div>useMemo提升性能</div>
<button onClick={() => setName(new Date().getTime())}>{name}</button>
<button onClick={() => setContent(new Date().getTime())}>{content}</button>
<Child name={name}>{content}</Child>
</div>
)
}

// 不使用useMemo,会造成不必要的性能开销
// function Child({name,children}){
// function changeName(name){
// console.log('child执行了');
// return name + '改变';
// }
// const otherName = changeName(name);
// return (
// <div>
// <div>{otherName}</div>
// <div>{children}</div>
// </div>
// )
// }

// 使用useMemo
function Child({ name, children }) {
function changeName(name) {
console.log('child执行了');
return name + '改变';
}
const otherName = useMemo(() => changeName(name),[name])
return (
<div>
<div>{otherName}</div>
<div>{children}</div>
</div>
)
}

export default Example7;

自定义Hook函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import React, { useState,useEffect,useCallback } from 'react'

function useWinSize(){
const [size,setSize] = useState({
width:document.documentElement.clientWidth,
height:document.documentElement.clientHeight,
})

const onResize = useCallback(()=>{
setSize({
width:document.documentElement.clientWidth,
height:document.documentElement.clientHeight,
})
},[])

useEffect(()=>{
window.addEventListener('resize',onResize);
return ()=>{
window.removeEventListener('resize',onResize);
}
})
return size;
}


function Example8() {
const size = useWinSize();
return <div>
<p>自定义Hook函数</p>
<p>页面size:{size.width} + {size.height}</p>
</div>
}

export default Example8;

ps:入口文件就只是引入而已.
index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import Example1 from './Example1';
import Example2 from './Example2';
import Example3 from './Example3';
import Example4 from './Example4';
import Example5 from './Example5';
import Example6 from './Example6';
import Example7 from './Example7';
import Example8 from './Example8';

import ExampleClass from './Example-class';


ReactDOM.render(
<div>
{/* <ExampleClass /> */}
<Example1 />
<hr/>
<Example2 />
<hr/>
<Example3/>
<hr/>
<Example4/>
<hr/>
<Example5/>
<hr/>
<Example6/>
<hr/>
<Example7/>
<hr/>
<Example8/>
</div>,
document.getElementById('root')
);

React Hooks 的优点

1
2
3
4
5
6
7
8
通过 Hooks 我们可以对 state 逻辑进行良好的封装,轻松做到隔离和复用,优点主要体现在:

复用代码更容易:hooks 是普通的 JavaScript 函数,所以开发者可以将内置的 hooks 组合到处理 state 逻辑的自定义 hooks中,这样复杂的问题可以转化一个单一职责的函数,并可以被整个应用或者 React 社区所使用;
使用组合方式更优雅:不同于 render props 或高阶组件等的模式,hooks 不会在组件树中引入不必要的嵌套,也不会受到 mixins 的负面影响;
更少的代码量:一个 useEffect 执行单一职责,可以干掉生命周期函数中的重复代码。避免将同一职责代码分拆在几个生命周期函数中,更好的复用能力可以帮助优秀的开发者最大限度降低代码量;
代码逻辑更清晰:hooks 帮助开发者将组件拆分为功能独立的函数单元,轻松做到“分离关注点”,代码逻辑更加清晰易懂;
单元测试:处理 state 逻辑的自定义 hooks 可以被独立进行单元测试,更加可靠;


整理自:

React Hooks完全上手指南-蚂蚁 RichLab 前端团队

React Hooks 入门教程-阮一峰

轻松学会 React 钩子:以 useEffect() 为例-阮一峰

bilibili

本文源码