2018年底 FaceBook 的 React 小组推出 Hooks。React Hooks
就是用函数的形式代替原来的继承类的形式,并且使用预函数的形式管理state,有Hooks可以不再使用类的形式定义组件了。原来把组件分为有状态组件和无状态组件,有状态组件用类的形式声明,无状态组件用函数的形式声明。现在所有的组件都可以用函数来声明了。
创建一个项目进行学习演示
mkdir ReactHooksDemo
cd ReactHooksDemo
create-react-app demo
npm start 之后,开启项目,开始 react-hooks 的学习
编写之前,将 src/index.js 进行删减
import React from 'react';
import ReactDOM from 'react-dom';
ReactDOM.render(
document.getElementById('root')
);
1. react-hooks 与之前的 react 有什么不同,可以编写相同的功能,进行比对
(写一个点击按钮,数量不断增加)
新建一个 Example.js
import React, { Component } from 'react';
class Example extends Component {
constructor(props) {
super(props);
this.state = { count: 0 }
}
render() {
return (
<div>
<p>点击了 {this.state.count} 次</p>
<button onClick={this.addCount.bind(this)}>点击</button>
</div>
);
}
addCount() {
this.setState({ count: this.state.count + 1 })
}
}
export default Example;
react-hooks
import React, { useState } from 'react'
function Example() {
const [count, setCount] = useState(0)
return (
<div>
<p>点击了 {count} 次</p>
<button onClick={() => setCount(count + 1)}>点击</button>
</div>
)
}
export default Example
不说其他的,代码量很明显就少了很多。
hooks
的目的就是让你不再写class
,让function
一统江湖。
2. useState 是 react 自带的一个 hook 函数,它的作用是用来声明状态变量。
声明的方式
const [ count , setCount ] = useState(0);
useState
函数接收的参数是状态的初始值,返回一个数组,这个数组的第0位是当前的状态值,第1位是可以改变状态值的方法函数。上面的代码的意思是声明了一个状态变量为 count,并把它的初始值设为0,同时提供了一个可以改变count的状态值的方法函数。
声明一个状态之后,接下来就需要读取状态中的值。
<p>点击了 {count} 次</p>
是不是超级简单
接下来改变 State 中的值
<button onClick={() => setCount(count + 1)}>点击</button>
之前我们说过 useState 返回一个数组,数组的第一位就是改变状态值的方法函数。直接调用setCount函数,这个函数接收的参数是修改过的新状态值。
声明多个状态值
import React, { useState } from 'react'
function Example() {
const [react, setReact] = useState('react')
const [vue, setVue] = useState('vue')
const [angular, setAngular] = useState('angular')
return (
<div>
<p>{react}</p>
<p>{vue}</p>
<p>{angular}</p>
</div>
)
}
export default Example
React Hooks不能出现在条件判断语句中,因为它必须有完全一样的渲染顺序。
3. Class
制作组件时,经常会用生命周期函数,来处理一些额外的事情(副作用:和函数业务主逻辑关联不大,特定时间或事件中执行的动作,比如Ajax请求后端数据,添加登录监听和取消登录,手动修改DOM等等。在 React Hooks
中也需要这样类似的生命周期函数,比如在每次状态(State)更新时执行,它为我们准备了 useEffect
。
原先的写法
(在页面渲染之后会控制台输出 componentDidMount 0)
(之后每一次点击,改变 count 的值,控制台就会输出 componentDidUpdate 1, 2, 3…)
import React, { Component } from 'react';
class Example extends Component {
constructor(props) {
super(props);
this.state = { count: 0 }
}
componentDidMount() {
console.log(`componentDidMount ${this.state.count}`)
}
componentDidUpdate() {
console.log(`componentDidUpdate ${this.state.count}`)
}
render() {
return (
<div>
<p>点击了 {this.state.count} 次</p>
<button onClick={this.addCount.bind(this)}>点击</button>
</div>
);
}
addCount() {
this.setState({
count: this.state.count + 1
})
}
}
export default Example;
react-hooks
import React, { useState, useEffect } from 'react'
function Example() {
const [count, setCount] = useState(0)
useEffect(() => {
console.log(`useEffect ${count}`)
})
return (
<div>
<p>点击了 {count} 次</p>
<button onClick={() => setCount(count + 1)}>点击</button>
</div>
)
}
export default Example
一行代码即可解决。
(在页面渲染之后会控制台输出 useEffect 0)
(之后每一次点击,改变 count 的值,控制台就会输出 useEffect 1, 2, 3…)
首先,声明了一个状态变量
count
,它的初始值设为 0,然后告诉 react,我们的这个组件有一个副作用。给 useEffect 传一个匿名函数,这个匿名函数就是我们的副作用,打印了一句话。当 React 要渲染组件时,它会记住用到的副作用,然后执行一次。等Reac
t 更新了State
状态时,它再一次执行定义的副作用函数。
使用 useEffect 的注意点
React 首次渲染和之后的每次渲染都会调用一遍
useEffect
函数 (从我们刚刚控制台的输出情况可以看出) ,而之前我们要用两个生命周期函数分别表示首次渲染 (componentDidMonut
) 和更新导致的重新渲染 (componentDidUpdate
) 。
useEffect
中定义的函数,执行时不会阻碍浏览器更新视图,也就是说这些函数时异步执行
的,而componentDidMonut
和componentDidUpdate
中的代码都是同步执行的。
4. useEffect 实现 componentWillUnmount生命周期函数(组件将要被卸载时执行)定时器要清空,避免发生内存泄漏;比如登录状态要取消掉,避免下次进入信息出错,解绑副作用
为了演示用 useEffect
来实现类似 componentWillUnmount
效果,安装 React-Router
npm install --save react-router-dom
src / index.js
import { BrowserRouter as Router, Route, Link } from "react-router-dom"
声明两个简单的 hooks
function Index() {
return (
<h2>index pages</h2>
)
}
function List() {
return (
<h2>list pages</h2>
)
}
增加路由
return (
<div>
<p>点击了 {count} 次</p>
<button onClick={() => setCount(count + 1)}>点击</button>
<Router>
<ul>
<li>
<Link to="/">index - 首页</Link>
</li>
<li>
<Link to="/list/">list - 列表</Link>
</li>
</ul>
<Route path="/" exact component={Index}></Route>
<Route path="/list/" exact component={List}></Route>
</Router>
</div>
)
确认页面渲染无误后,我们给两个新增的 hooks 函数增加 useEffect
function Index() {
useEffect(() => {
console.log('useEffect index 页面 进入')
})
return (
<h2>index pages</h2>
)
}
function List() {
useEffect(() => {
console.log('useEffect list 页面 进入')
})
return (
<h2>list pages</h2>
)
}
这时候我们点击Link进入任何一个组件,在浏览器中都会打印出对应的一段话。此时可以用返回一个函数的形式进行解绑
function Index() {
useEffect(() => {
console.log('useEffect index 页面 进入')
return () => {
console.log('index 页面 卸载了')
}
})
return (
<h2>index pages</h2>
)
}
当你离开 index 页面时,控制台就会打印出 index 页面卸载。其实每次状态发生变化,useEffect都进行了解绑。
实现类似
componentWillUnmount
的效果,需要请出useEffect
的第二个参数,它是一个数组,数组中可以写入很多状态对应的变量,意思是当状态值发生变化时,才进行解绑
。但是当传空数组 [] 时,就是当组件将被销毁时才进行解绑,这也就实现了componentWillUnmount
的生命周期函数。
function Index() {
useEffect(() => {
console.log('useEffect index 页面 进入')
return () => {
console.log('index 页面 卸载了')
}
}, [])
return (
<h2>index pages</h2>
)
}
我们给计数组件加上 useEffect 解绑作用
useEffect(() => {
console.log(`useEffect=>点击了 ${count} 次`)
return () => {
console.log('走了走了')
}
}, [count])
这时候只要 count 状态发生变化,都会执行解绑副作用函数,浏览器的控制台也就打印出了一串‘ 走了走了’。
5. useContext 让父子组件传值更简单
在用类声明组件时,父子组件的传值是通过组件属性和
props
进行的,现在使用方法(Function)
来声明组件,已经没有了constructor
构造函数也就没有了props的接收,那父子组件的传值就成了一个问题。React Hooks
为我们准备了useContext
。useContext
,可以帮助我们跨越组件层级直接传递变量,实现共享。
想要使用就要创建它,使用 createContext
创建 context
import React, { useState, createContext } from 'react'
const CountContext = createContext()
function Example() {
const [count, setCount] = useState(0)
return (
<div>
<p>点击了 {count} 次</p>
<button onClick={() => setCount(count + 1)}>点击</button>
<CountContext.Provider value={count}>
</CountContext.Provider>
</div>
)
}
export default Example
把 count
变量允许跨层级实现传递和使用了(也就是实现了上下文),当父组件的 count
变量发生变化时,子组件也会发生变化。使用 createContext 创建了一个名为 CountContext 的上下文环境,想要把 count 这个值 传入给子组件,子组件要放在
<CountContext.Provider value={count}>
</CountContext.Provider>
环境之中,才可以接收到传入的值
使用 useContext
来接收
import React, { useState, createContext, useContext } from 'react'
function Counter(){
const count = useContext(CountContext)
return (
<h2>{count}</h2>
)
}
<CountContext.Provider value={count}>
<Counter></Counter>
</CountContext.Provider>
点击按钮的时候,子组件 Counter,就可以接收到了。
6. useReducer介绍和简单使用。reducer其实就是一个函数,这个函数接收两个参数,一个是状态,一个用来控制业务逻辑的判断参数。
之前我们计数器只有加,现在我们来个减法
function countReducer(state, action) {
switch(action.type) {
case 'add':
return state + 1
case 'sub':
return state - 1
default:
return state
}
}
上面的代码就是Reducer,你主要理解的就是这种形式和两个参数的作用,一个参数是状态,一个参数是如何控制状态。
useReducer
,是React hooks
提供的函数,可以增强我们的Reducer
,实现类似Redux
的功能,用useReducer实现计数器的加减双向操作
import React, { useReducer } from 'react'
function ReducerDemo() {
const [count, dispatch] = useReducer((state, action) => {
switch(action) {
case 'add':
return state + 1
case 'sub':
return state - 1
default:
return state
}
}, 0)
return (
<div>
<p>点击了 {count} 次</p>
<button onClick={() => dispatch('add')}>加一</button>
<button onClick={() => dispatch('sub')}>减一</button>
</div>
)
}
export default ReducerDemo
7. useMemo 优化 React Hooks 程序性能
useMemo
主要用来解决使用React hooks产生的无用渲染的性能问题
。使用 function 的形式来声明组件,失去了shouldCompnentUpdate
(在组件更新之前)这个生命周期,也就是说我们没有办法通过组件更新前条件来决定组件是否更新。而且在函数组件中,也不再区分 mount 和 update 两个状态,这意味着函数组件的每一次调用都会执行内部的所有逻辑
,就带来了非常大的性能损耗。useMemo
和useCallback
都是解决上述性能问题的,
import React, {useState, useMemo } from 'react'
function ReducerDemo() {
const [vue, setVue] = useState('vue 来了')
const [react, setReact] = useState('react 来了')
return (
<div>
<button onClick={() => setVue(new Date().getTime())}>{vue}</button>
<button onClick={() => setReact(new Date().getTime())}>{react}</button>
<ChildComponent name={vue}>{vue}</ChildComponent>
</div>
)
}
function ChildComponent({ name, children }) {
function changeVue(name) {
console.log('vue 来了·······')
return name + 'vue 真的来了'
}
const actionVue = changeVue(name)
return (
<>
<div>{actionVue}</div>
<div>{children}</div>
</>
)
}
export default ReducerDemo
在浏览器中点击 react 按钮,vue 对应的方法都会执行,结果虽然没变,但是每次都执行,这就是性能的损耗。
而我们要实现的是,只有点击 vue 的时候,才会改变 vue
const actionVue = useMemo(() => {
changeVue(name)
}, [name])
浏览器中点击 react 按钮,changeVue 就不再执行了。也节省了性能的消耗。
8. useRef获取DOM元素
import React, {useState, useMemo, useRef } from 'react'
function ReducerDemo() {
const input = useRef(null)
const getInputRef = () => {
input.current.value = 'hello react-hooks'
console.log(input)
}
return (
<div>
<input ref={input} type="text" />
<button onClick={getInputRef}>获取 ref 值</button>
</div>
)
}
export default ReducerDemo
点击按钮时,可以看到在浏览器中的控制台完整的打印出了 DOM 的所有东西,并且界面上的<input/>
框的 value 值也输出了我们写好的 ‘hello react-hooks’ 。说明我们可以使用 useRef
获取DOM元素,并且可以通过 useRef
控制DOM的属性和值。
9.自定义 hooks 函数
其实自定义 Hooks 函数和用 Hooks 创建组件很相似,我们平时用 JavaScript 写函数几乎一模一样,就是多 React Hooks的特性,自定义Hooks函数偏向于功能,而组件偏向于界面和业务逻辑。
自定义 Hooks 函数,记住一定要用use开头
,这样才能区分出什么是组件,什么是自定义函数。
获取浏览器窗口大小
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 Example() {
const size = useWinSize()
return (
<div>
size: {size.width} × {size.height}
</div>
)
}
参考文章:https://jspang.com/detailed?id=50#toc212