React Hooks
React Hooks介绍
React Hooks是对函数型组件进⾏增强, 让函数型组件可以存储状态, 可以拥有处理副作⽤的能⼒。让开发者在不使⽤类组件的情况下, 实现相同的功能。从React16.8开始,react官方推荐使用函数创建组件,当然也不会放弃对类组件的支持。
什么是副作用呢?在一个组件当中,只要不是把数据转换成数据的代码,就是副作用。例如获取DOM元素、为DOM元素添加事件、设置定时器以及发送ajax请求,这都属于副作用代码。在类组件中,我们通常使用生命周期函数来处理副作用,在函数式组件中,我们就需要使用hooks来处理副作用。
类组件的不足
React中类组件的不足就是hooks要解决的问题。
- 缺少逻辑复⽤机制:为了复⽤逻辑增加⽆实际渲染效果的组件,增加了组件层级显示⼗分臃肿。增加了调试的难度以及运⾏效率的降低
- 类组件经常会变得很复杂难以维护:将⼀组相⼲的业务逻辑拆分到了多个⽣命周期函数中。在⼀个⽣命周期函数内存在多个不相⼲的业务逻辑
- 类成员⽅法不能保证this指向的正确性
React Hooks使用
Hooks 意为钩⼦, React Hooks 就是⼀堆钩⼦函数, React 通过这些钩⼦函数对函数型组件进⾏增强, 不同的钩⼦函数提供了不同的功能。
- useState()
- useEffects()
- useReducer()
- useRef()
- useCallback()
- useContext()
- useMemo()
useState
功能是让函数型组件保存状态,在我们的认知中,函数中的变量执行完后会被释放掉,所以函数型组件不可以保存状态数据。 现在有useState就可以保存状态数据了,内部使用闭包来保存状态数据。
import React, {useState} from 'react';
function App(){
const [count,setCount] = useState(0);
return <div>
<span>{count}</span>
<button onClick={()=>setCount(count+1)}>+1</button>
</div>;
}
当状态值修改后,组件会重新渲染显示最新的状态值。
- 接收唯⼀的参数即状态初始值. 初始值可以是任意数据类型.
- 返回值为数组. 数组中存储状态值和更改状态值的⽅法. ⽅法名称约定以set开头, 后⾯加上状态名称.
- ⽅法可以被调⽤多次. ⽤以保存不同状态值.
- 参数可以是⼀个函数, 函数返回什么, 初始状态就是什么, 函数只会被调⽤⼀次, ⽤在初始值是动态值的情况
设置状态值⽅法的参数可以是⼀个值也可以是⼀个函数,设置状态值⽅法的⽅法本身是异步的。
useReducer
useReducer是另⼀种让函数组件保存状态的⽅式,它的状态保存在特定的地方,如果组件想要更改其状态,要通过dispatch来触发一个action,这个action会被reducer函数接收到,在reducer内部会判断函数类型是什么,从而决定状态如何处理,最后通过返回值的方式来更新状态。
import React,{useReducer} from 'react';
function reducer(state,action){
switch(action.type){
case 'increment':
return state + 1;
}
}
function App(){
const [count,dispatch] = useReducer(reducer,0);
return <div>
<span>{count}</span>
<button onClick={()=>dispatch({type:'increment'})}>+1</button>
</div>;
}
useContext
用于在跨组件层级获取数据时简化获取数据的代码。
import { createContext, useContext } from 'react';
const countContext = createContext();
function App(){
return <countContext.Provider value = {100}>
<Foo />
</countContext.Provider>
}
function Foo(){
const count = useContext(countContext);
return <div>{count}</div>
}
useEffect
它的作用是让函数型组件拥有处理副作用的能力,类似生命周期函数,在类组件中使用生命周期函数来处理副作用,在函数型组件中使用useEffect处理副作用。
useEffect执行时机
可以把 useEffect 看做 componentDidMount, componentDidUpdate 和 componentWillUnmount 这三个函数的组合,也就是会在组件挂载之后执行,会在组件数据更新完成之后执行,会在组件卸载之前执行。
它的语法是:
- useEffect(() => {}) => componentDidMount, componentDidUpdate,也就是会在组件挂载和更新完成之后执行
- useEffect(() => {}, []) => componentDidMount ,在组件挂载完成后执行一次
- useEffect(() => () => {}) => componentWillUnMount,传入的回调函数有一个返回值,该返回值是一个函数,该返回值函数会在组件卸载之前执行,做清理工作。
useEffect使用方法
- 为window对象添加滚动事件
- 设置定时器让count数值每隔⼀秒增加1
import React,{useEffect,useState} from "react";
import ReactDOM from "react-dom";
function App(){
function onScroll(){
console.log('页面发生滚动了');
}
useEffect(()=>{
window.addEventListener('scroll',onScroll);
return ()=>{
window.removeEventListener('scroll',onScroll);
}
},[])
const [count,setCount] = useState(0);
useEffect(()=>{
const timerId = setInterval(()=>{
setCount(()=>count + 1);
},1000);
return ()=>{
clearInterval(timerId);
}
},[]);
return <div>
<span>{count}</span>
<button onClick={()=>ReactDOM.unmountComponentAtNode(document.getElementById('root'))}>卸载组件</button>
</div>;
}
export default App;
useEffect 解决的问题
通过useEffect处理副作用,相比类组件有什么优势呢?
- 按照⽤途将代码进⾏分类 (将⼀组相⼲的业务逻辑归置到了同⼀个副作⽤函数中) :由于useEffect可以多次调用,我们可以按照用途,把不同用途的代码放在不同的useEffect中,进行分类。
- 简化重复代码, 使组件内部代码更加清晰:一般情况下组件挂载要做的和更新之后做的事情是一样的,使用useEffect将两个api替换成一个api,简化代码。
useEffect数据监测
只有指定数据发生变化时触发effect。
useEffect(()=>{
document.title = count;
},[count]);
useEffect结合异步函数
useEffect中的参数函数不能是异步函数, 因为useEffect函数要返回清理资源的函数, 如果是异步函数就变成了返回Promise
useEffect(()=>{
(async ()=>{
await axios.get()
})()
})
useMemo
- useMemo 的⾏为类似Vue中的计算属性, 可以监测某个值的变化, 根据变化值计算新值
- useMemo 会缓存计算结果. 如果监测值没有发⽣变化, 即使组件重新渲染, 也不会重新计算. 此⾏为可以有助于避免在每个渲染上进⾏昂贵的计算
import {useMemo} from 'react';
const result = useMemo(()=>{
//如果count值发生变化此函数重新执行
return result;
},[count])
memo方法
该方法的作用是在组件重新渲染之前,看一下组件中的数据有没有发生变化,如果组件中的数据没有变化,它就阻止当前组件进行重新渲染,它是用来做性能优化的。类似于类组件中的PureComponent和shouldComponentUpdate生命周期函数,如果我们要在类组件实现相同的功能,我们可以让类组件继承PureComponent,也可以在类组件的shouldComponentUpdate生命周期函数中判断数据有没有发生变化,没有return false阻止组件重新渲染。
import React, {memo} from 'react';
function Counter(){
return <div></div>;
}
export default memo(Counter);
useCallback
它的作用是缓存函数, 使组件重新渲染时得到相同的函数实例,它也是用来做性能优化的。
import React, {useCallback} from 'react';
function Counter(){
const [count,setCout] = useState(0);
const resetCount = useCallback(()=>setCount(0),[setCount]);
return <div>
<span>{count}</span>
<button onClick={()=>setCount(count+1)}>+1</button>
<Test resetCount={resetCount}/>
</div>
}
import {memo} from 'react';
function Test(props){
console.log('Test re-render');
return <div>
Test
<button onClick={props.resetCount}>reset</button>
</div>
}
useRef
它用来获取DOM元素对象,useRef创建的对象有current属性,该属性就是你要获取的DOM对象。
import React, {useRef} from 'react';
function App(){
const username = useRef();
const handler = ()=>console.log(usename); //{current:input}
return <input ref={username} onChange={handler}/>
}
useRef还有另外一个功能就是保存数据,保存的数据有一个特点就是跨组件周期的。即使组件重新渲染,保存的数据仍然还在,保存的数据被更改不会触发组件重新渲染。
import React, { useState, useEffect, useRef} from 'react';
function App(){
const [count,setCount] = useState(0);
let timerId = useRef();
useEffect(()=>{
timerId.current = setInterval(()=>{
setCount(count=>count+1)
},1000)
},[])
const stopCount=()=>{
console.log(timerId)
clearInterval(timerId.current)
}
return <div>
{count}
<button onClick={stopCount}>停止</button>
</div>;
}
export default App;
自定义hook函数
自定义hook就是自己创建的hook函数,⾃定义 Hook 是标准的封装和共享逻辑的⽅式,意思是在组件的内部,有哪些逻辑是共享的,其他组件也可能使用到的逻辑,这时候就可以把逻辑写到自定义hook当中,在哪个组件想要使用逻辑就调用自定义hook即可。
⾃定义 Hook 是⼀个函数, 其名称以 use 开头。⾃定义 Hook 其实就是逻辑和内置 Hook 的组合。
import React, {useState,useEffect} from 'react';
import axios from 'axios';
function useGetPost(){
const [post,setPost] = useState({});
useEffect(()=>{
axios.get('http://www.xxx.com')
.then(response=>setPost(response.data));
},[]);
return [post,setPost]
}
function App(){
const [post,setPost] = useGetPost();
return <div>
<div>{post.title}</div>
<div>{post.body}</div>
</div>
}
import React,{useState,useEffect} from 'react';
function useUpdateInput(initialValue){
const [value,setValue] = useState(initialValue);
return {
value,
onChange:event=>setValue(event.target.value)
}
}
function App(){
const usernameInput = useUpdateInput('');
const passwordInput = useUpdateInput('');
const submitForm = event=>{
event.preventDefault();
console.log(usernameInput.value);
console.log(passwordInput.value);
}
return <form onSubmit={submitForm}>
<input type="text" name="username" {...usernameInput} />
<input type="password" name="password" {...passwordInput} />
<input type="submit" />
</form>
}
React路由Hooks
react-router-dom路由提供了4个钩子函数
- useHistory
- useLocation
- useRouteMatch
- useParams
这些钩子函数的作用就是获取相关的路由信息, 当进入某个路由组件的时候,组件的props下面就会多出几个对象,比如history、location、match,这些钩子函数的作用就是为了获取这几个对象信息。