React Hook
Hook 是 React 16.8 的新增一系列封装函数,它可以让函数组件中可以使用state,跨组件通信,生命周期等React的特性
Hook的意义在于:将函数组件中的业务逻辑抽离出来,保证组件UI的纯净化
内置Hook (10个)
1.useState
定义和修改state [修改state的方法得到的一定是新值]
基本语法:
//定义state和修改state的方法
const [name,setName]=useState<数据类型>(数据默认值)
//定义事件处理程序来修改state
const change=()=>{
// setName()
// setName((旧值)=>{return 修改后的值})
}
案例一:定义修改字符串
import Reac, { FC,useState} from "react";
interface IAppProps{}
const App:FC<Partial<IAppProps>>=()=>{
// todo 定义state和义修改state的方法
const [name,setName]=useState<string>('篮网')
// todo 定义事件处理程序
const set=()=>{setName('掘金')}
return <div>
<button onClick={set}>点击</button>
<p>{name}</p>
</div>
}
export default App;
案例二:定义修改数值(计数案例)
import Reac, { FC,useState} from "react";
interface IAppProps{}
const App:FC<Partial<IAppProps>>=()=>{
// todo 定义state和义修改state的方法
const [count,addCount]=useState<number>(1);
// todo 定义事件处理程序
const add=()=>{addCount((n)=>n+1)}
return <div>
<button onClick={add}> + {count} </button>
</div>
}
export default App;
案例三:定义修改数值(计数案例)
import Reac, { FC,useState} from "react";
interface IAppProps{}
const App:FC<Partial<IAppProps>>=()=>{
// todo 定义state和义修改state的方法
const [count,addCount]=useState<number>(1);
// todo 定义事件处理程序
const add=()=>{addCount((n)=>n+1)}
return <div>
<button onClick={add}> + {count} </button>
</div>
}
export default App;
案例四:定义修改对象
import Reac, { FC,useState} from "react";
interface IAppProps{}
interface IObj{name:string}
const App:FC<Partial<IAppProps>>=()=>{
const [obj,setObj]=useState<Partial<IObj>>({})
const giveObjName=()=>{
// !! 下面的代码不能写
// obj.name = 'lakers';
// setObj(obj);
// todo1 写法1
// setObj({name:'篮网'})
// todo1 写法2
obj.name='篮网'
setObj({...obj})
}
return <div>
<button onClick={giveObjName}>给对象值</button>
<p>{obj.name}</p>
</div>
}
案例五:定义修改数组
import React, { FC, useState } from "react";
interface IAppProps {}
interface IList {
id: number;
task: string;
flag: boolean;
}
const App: FC<Partial<IAppProps>> = () => {
// todo 定义state和义修改state的方法
const [list, setList] = useState<Partial<IList>[]>([]);
// todo 定义事件处理程序
const add = (e: any) => {
if (e.keyCode === 13) {
const obj = {
id: list.length + 1,
task: e.target.value,
flag: false,
};
setList([obj, ...list]);
e.target.value=''
}
};
return (
<div>
<input type="text" onKeyDown={ add } />
<ul>{list.map((item) => (<li key={item.id}>{item.task}</li>))}</ul>
</div>
);
};
export default App;
2.useReducer 定义和修改state
类似于类似 Redux 的功能
定义state以及修改state
可以理解为做出来类似vuex的效果
可以理解它是一个简易版本的状态管理工具
它接收一个如
(state, action) => newState
的 reducer和一个初始化的state,并返回当前的 state 以及与其配套的dispatch
方法作者:zidea
链接:https://www.jianshu.com/p/14e429e29798
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
写法一:
const [state,dispatch]=useReducer(reducer,initialState)
写法二:
const [state,dispatch]=useReducer((state,action)=>{},初始化的state)
案例:计数案例
流程 button --> add – dispatch发布 —> reducer的action接收 ----> state
import React, { FC, useReducer } from "react";
interface IInitialState{
count:number
}
const initialState:IInitialState={
count:0
}
const reducer=(state:any,action:any) => {
switch (action.type){
case 'add':
return {count:state.count+1}
case 'jian':
return {count: state.count - 1 }
default:
return initialState
}
}
const App:FC=() => {
const [state,dispatch]=useReducer(reducer,initialState)
const add=()=>{
dispatch({type:'add'})
}
const jian=()=>{
dispatch({type:'jian'})
}
return (
<div>
<button onClick={add}>+</button>
<button onClick={jian}>-</button>
<p>{state.count}</p>
</div>
)
}
export default App
3.useEffect
useEffect是用于实现生命周期功能的,他有3三种用法
语法:
语法: useEffect(callback,[数据])
数据:数据可以多个,可为props,state
**用法1:componentDidMount **
useEffect(() => {}, [])
类似componentDidMount 钩子函数功能 :组件挂载结束
自动执行一次,可以进行真实DOM操作,数据请求发送
用法2:componentDidUpdate
useEffect(() => {}, [数据])
类似componentDidUpdate钩子函数功能 :组件更新结束 表示对这个数据做监听
数据改变,就会触发,执行多次
**用法3:componentWillUnmount **
//callback的返回值为一个函数 useEffect(() => { return () => {} }) // 第二个参数随意
类似componentWillUnmount 的功能 :组件销毁时触发
4.useLayoutEffect 类似useEffect
useLayoutEffect
1. 用法和useEffect一致
2. useLayoutEffect 解决了什么问题是useEffect解决不了的?
- 其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新
3. 理解
- 范围不一致,useLayoutEffect的更大
4. 什么时候用它?
- 如果你正在将代码从 class 组件迁移到使用 Hook 的函数组件
- 我们推荐你一开始先用 useEffect,只有当它出问题的时候再尝试使用 useLayoutEffect。
5.useRef ref绑定
通过ref绑定可以拿到类子组件的数据或方法
ref无法绑定函数子组件,但子组件使用forwardRef高阶组件可以解决
绑定类组件
// 父组件
const 绑定名: any = useRef(null) //拿到子组件实例
绑定名.current.数据/方法 //使用子组件的数据或方法
<子组件 ref={绑定名}> //绑定子组件
绑定函数组件
ref无法绑定函数子组件,可以通过forwardRef高阶组件解决
- forwardRef
forwardRef 我们称之为 高阶组件[HOC] High Order Component
它是一个函数,接收一个组件作为参数,并返回一个新组件
高阶组件的原理是闭包
// 父组件绑定和使用如绑定类组件
// 子组件导出时,导出新组件
export default React.forwardRef(Hello)
手动封装forwardRe
// 闭包
const HocComp = ( Comp: any ) => {
return class _ extends React.Component{
render () {
return <Comp/>
}
}
}
6.usemperativeHandle
和ref绑定搭配,我们将子组件的内容绑定到父组件的ref中
-
搭配ref绑定来使用的
-
专用于函数组件
-
他有两个参数:第一个为ref,第二个为回调函数
-
它的返回值就是父组件ref绑定获得的结果
-
注意第一个参数ref的类型约定
// ref的类型约定
// ref: React.Ref<unknown>
useImperativeHandle(ref,() => {
return {可以返回数据或方法}
})
7.useContext 跨组件通信
创建上下文context对象
// src/context/index.ts
import React from 'react'
const ctx = React.creatContext(0)
export default ctx
数据源组件
import ctx from './context'
....
return (
<ctx.Provide value={money}>
<接收组件>
</ctx.Provid>
)
接收组件(类组件)
//引入上下文组件
import {useContext}
import ctx from './context'
//从ctx拿到数据实例
static contextType=ctx;
//使用数据
return(
<div>{this.context}<div>
)
接收组件(函数组件)
//引入上下问组件
import {useContext}
import ctx from './context'
//从ctx拿到数据实例
const money=useContext(ctx)
//使用数据
return(
<div>{money}<div>
)
8.useMeno 组件性能优化
问题一: 类组件中通过 shouldComponentUpdate 达到性能优化目的,那么函数组件呢?
解决: Hook ( useCallback useMemo )
问题二: 函数组件 - 重复渲染问题
因为函数组件本身是函数,所以只要数据改变,它就要重新执行自身总结
state的改变,会引起组件的重复调用,那么渲染就会重复执行,这样会带来性能损耗
解决: useMemo/useCallback
语法:
const memoValue =useMemo(()=>{当监听数据改变时,才执行这里的渲染代码},监听数据)
//该函数会得一个返回值,通常被称为记忆值
案例:优化函数组件的渲染
import React, { FC, useState,useMemo} from "react";
const App:FC=()=>{
const [n,setN]=useState<number>(0)
const [list,setList]=useState<Array<number>>([1,2,3,4])
const add=()=>{
setN((n)=>n+1)
}
const renderList=()=>{
console.log('render');
return list.map((item,index)=>( <li key={index}>{item}</li> ))
}
// memoValue为记忆值
const memoValue= useMemo(() => renderList(), [list])
const addList=()=>{
setList(
[Math.max(...list)+1,...list]
)}
return (
<div>
<button onClick={add}>+ {n}</button>
<hr />
<button onClick={addList}>加一条数据</button>
<ul>
{memoValue}
</ul>
</div>
)
}
export default App
9.useCallback 组件性能优化
父组件将渲染函数传递给子组件,但当父组件数据更新时,会引起子组件不必要的更新,所以可以使用useCallback得到一个返回值为渲染函数的记忆值,使用记忆值来代替传递的渲染函数
useCallback + memo【高阶组件】
// 父组件
import React, { FC, useState, useCallback } from "react";
import Hello from "./Hello";
const App: FC = () => {
const [count, setCount] = useState<number>(0);
const [list, setList] = useState<Array<number>>([1, 2, 3, 4]);
const add = () => {
setCount((n) => n + 1);
};
const renderList = () => {
console.log('render')
return list.map((item,index) => <li key={index}> { item } </li>)
}
// todo 得到一个记忆函数
const memoFunc = useCallback(() => {
return renderList()
}, [list])
return (
<div>
<button onClick={add}> + {count} </button>
<hr/>
<Hello memoFunc = { memoFunc } />
</div>
);
};
export default App;
// 子组件
import React, { ReactElement,memo } from 'react'
interface Props {
memoFunc: () => ReactElement[]
}
function Hello({memoFunc}: Props): ReactElement {
return (
<div>
<ul>
{ memoFunc() }
</ul>
</div>
)
}
export default memo(Hello)
10.useDebugValue 自定义Hook起名
给自定义Hook起名[ 谷歌调试插件显示 ]
自定义Hook就是一个封装函数
名字前缀 useXxx
注意: hook不可以嵌套使用
// 自定义Hook src/hooks/index.ts
import { useDebugValue, useEffect, useState } from "react";
const useDemo=(Flag:boolean)=>{
const [txt,setText]=useState<string>('');
useEffect(()=>{
Flag? setText('进入我心里') :setText('QNMD')
},[])
useDebugValue('Flag')
return txt
}
export {useDemo}
自定义Hook库:
- https://ahooks.js.org/zh-CN/
- https://github.com/Junscuzzy/useHooks.ts