1, 为什么会有Hooks?
React组件创建的方式有两种,一种是类组件,一种是纯函数组件,根据组件的设计原理,组件不要变成复杂的容器,最好只是数据流的管道,然后开发者根据需要,组合各种管道即可。也就是说:组件的最佳写法应该是函数,而不是类。
但是我们知道,在实际的开发中,类组件和函数式组件的区别是很大的,纯函数组件有着类组件不具备的多种特点,比如:
(1)纯函数组件没有状态
(2)纯函数组件没有生命周期
(3)纯函数组件没有this
(4)只能是纯函数
上面纯函数的特点导致:我们推崇的函数组件只能做UI展示的功能,涉及到状态的管理和切换,我们还是不得不用类组件或者redux, 但我们也知道类组件是有很多缺点的,比如:即使一个简单的页面,你的代码也会显得很重,并且每创建一个类组件,都要去继承一个React实例,至于redux更不用说了,记住一句话,能用React解决的问题就不要用Redux,
比如:一个简单的计数器用类组件实现:
import React from 'react'
class AddCount extends React.Component {
constructor(props) {
super(props)
this.state = {
count:0
}
}
addCount = () => {
let newCount = this.state.count
this.setState({
count: newCount -1
})
}
render() {
return (
<>
<p>{this.state.count}</p>
<button onClick={this.addCount}>Count--</button>
</>
)
}
}
export default AddCount;
可以看出来,上面的代码很重,为了解决这种,类组件功能齐全却很重,纯函数组件很轻便却又很多重大的限制,这个时候,React团队设计了React Hooks。
React Hooks就是加强版的函数组件, 我们完全可以不用Class的类组件,就可以写出一个功能齐全的函数式组件。
2, 什么是Hooks?
Hooks的意思就是钩子的意思,React Hooks的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部的代码钩进来,Hooks就是我们说的钩子,那么这些钩子怎么用了?你要写什么功能,就用什么钩子,所以常用的钩子函数我们要时刻记住,当然如果有特殊情况,我们可以自定义钩子。
# 常用的钩子
useState()
useContext()
useReducer()
useEffect()
记住:不同的钩子为函数引入不同的外部功能。熟记各种不同钩子的作用。我们发现上面的钩子都是use前缀,React约定,钩子一律使用use前缀命名,所以自己定义的钩子函数也要用useXXX形式命名
3, Hooks的用法
(1)useState()状态钩子
纯函数组件没有状态
useState创建一个状态,赋值一个初始值,当前赋值的初始值为0
数组的第一个是一个变量,此变量指向当前状态的值:this.state
数组的第二个是一个函数,次函数可以修改状态的值: this.setState
import React, { useState } from 'react';
function ExampleHook() {
const [age, setAge] = useState(20);
const [sex, sexSex] = useState('男');
const [work, setWork] = useState('前端程序员')
return (
<div>
<p>zlm今年 { age }</p>
<p>性别 { sex }</p>
<p>工作 {work}</p>
</div>
)
}
export default ExampleHook;
注意:
1, useState, 靠顺序记忆 2, 不能存在于条件判断语句中
(2)useContext()共享状态钩子
1, 解决父子组件传值, 父组件创建上文对象,
比如: CountContext = createContext()
2, 子组件使用useContext 接受,
const count = useContext(CountContext),
是哪个上下文传递的值,就把哪个值放入useContext中
import React, { useState, createContext, useContext } from 'react';
const CountContext = createContext();
function Counter() {
const count = useContext(CountContext)
return <h2>{count}</h2>
}
function ExampleHook() {
const [count, setCount] = useState(0)
return (
<div>
<p>You Clicked { count } times</p>
<button onClick={()=>{setCount(count + 1)}}>Click me</button>
<CountContext.Provider count={count}>
<Counter></Counter>
</CountContext.Provider>
</div>
)
}
export default ExampleHook;
(3) useReducer
1, useReducer 有两个参数,
第一参数是reducer函数, 第二个参数是当前操作的这个state的初始值 2, 第一参数reducer函数,里面也有两个值,第一个是state, 第二个是action,
3, 整个useReducer返回两个参数,第一个是count的值,第二个是dispatch派发器,传递一个action进去
import React, { useReducer } from 'react';
function ReducerDemo() {
const [count, dispatch] = useReducer((state, action) => {
debugger
switch(action) {
case 'add':
return state + 1
case 'sub':
return state -1
default:
return state
}
}, 0)
return (
<div>
<h2>现在的分数是:{count}</h2>
<button onClick={()=>{dispatch('add')}}>Increment</button>
<button onClick={()=>{dispatch('sub')}}>Decrement</button>
</div>
)
}
export default ReducerDemo;
注意:通过:useReducer, useContext 实现Redux
(4) useRef
1, 获取DOM元素 2, 保存变量
import React, { useState, useRef, useEffect } from 'react';
function Example8() {
const inputRef = useRef(null)
const onButtonClick = ()=> {
inputRef.current.value = 'Hello zlm'
console.log(inputRef)
}
const [text, setText] = useState('zlm')
const textRef = useRef()
useEffect(() => {
textRef.current = text
console.log(textRef)
})
return (
<>
<input ref={inputRef} type='text'></input>
<button onClick={onButtonClick}>在input上展示文字</button>
<br/>
<input value={text} onChange={(e) => {setText(e.target.value)}}></input>
</>
)
}
export default Example8;
(5) useEffect
1, useEffect里面是一个匿名方法,useEffect是异步的,有一个延时的动作, 2,useEffect 对应类组件中的componetDidMount, 和componentDidUpdate 这两个生命周期函数 3, useEffect第二个参数,(1) 如果第二个参数为空,只有组件销毁的时候,才会执行解绑函数,如果没有第二个参数,会一直执行解绑函数 useEffect里面的匿名方法中,return 是匿名解绑函数, 4, 当useEffect第二参数不为空的时候,
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, false)
console.log('我只想了一次')
return ()=> {
window.removeEventListener('resize')
}
},[])
return size;
}
function Example9() {
const size = useWinSize()
return (
<div>
页面的size: {size.width} X {size.height}
</div>
)
}
export default Example9;
(6)其他钩子
1,必须use开头 2, useCallback 缓存我们的方法, useMemo缓存我们的状态
3useMemo 解决性能问题
shouldCompentUpdate, 组件更新之前
如果父组件更新了,子组件也会跟着一起更新,这个时候就有性能问题
useMemo有两个参数,第一个参数一个匿名方法,第二个参数是控制匿名方法执行的变量,如果参数有改变,就不执行