介绍
在 React 中 有两种类型,一种是 class 一种是 函数式, 他们两个有何区别呢,那就是状态,class 相对于函数式的写法相对麻烦,但包含生命周期,可改变render,函数式组件结构简单,但无法动态的更改 render。
为了有效的使开发更加简洁,方便,React 在 16.8 的版本上 推出了 Hook,以此来解决 函数式组件 没有状态的问题,从而达到快速开发
另外 Ant Design pro V5 上也推崇 Hook 写法,所以我们来讲解下 Hook,让其更好的了解、使用 React ~
具体的项目展示:Domesy/Hook
useState
在 Class 中,我们定义变量在 constructor 中设置 this.state 设置变量,而在 Hook 中我们使用 useState
作用:
用来声明状态变量, 相当于 class 中的 this.state 和 this.setState 的作用
代码演示
详细代码
import React, { useState } from 'react';
import { Button } from 'antd';
const Mock: React.FC<any> = () => {
const [count, setCount ] = useState<number>(0)
return (
<div style={{display: 'flex', justifyContent: 'space-between', paddingRight: 200}}>
<Button type='primary' onClick={() => setCount(count + 1)}>加1</Button>
<div>{count}</div>
</div>
);
};
export default Mock;
useEffect
- 副作用(Side Effect)是指 function 做了和本身运算返回值无关的事,如请求数据、修改全局变量,打印、数据获取、设置订阅以及手动更改 React 组件中的 DOM 都属于副作用操作都算是副作用
- 整合原有的生命周期方法,通过第二个参数来接收
作用
副作用,主要是将组件中的 componentDidMount componentDidUpdate componentWillUnmount 到一个方法中,杜绝了频繁定义的繁琐
代码演示
详细代码
import React, { useState, useEffect } from 'react';
import { Button } from 'antd';
const Mock: React.FC<any> = () => {
const [count, setCount ] = useState<number>(0)
return (
<div>
<Button type='primary' onClick={() => setCount(count + 1)}>加一</Button>
<Test count={count} />
</div>
);
};
const Test: React.FC<{count: number}> = ({count}) => {
const [count1, setCount1 ] = useState<number | false>(false)
const [count2, setCount2 ] = useState<number | false>(false)
useEffect(() => {
setCount1(count)
},[])
useEffect(() => {
setCount2(count)
},[count])
return <div style={{display: 'flex', justifyContent: 'space-between', marginRight: 200, marginTop: 50}}>
<div>只执行一次: {count1}</div>
<div>执行多次: {count2}</div>
</div>
}
export default Mock;
useContext
- useContext 实现跨层级传递,实现数据共享
- 作用就是怼他包含的组件树提供全局共享的数据的一种技术
- 需要createContext的帮助,通过 CountContext.Provider 包裹的组件,才能通过 useContext 获取对应的值
作用
上下文,对应的Context,其本意就是设置全局共享数据,使所有组件可跨层级实现共享
代码演示
详细代码
import React, { useState, createContext, useContext } from 'react';
import { Button } from 'antd';
const CountContext = createContext(-1)
const Mock: React.FC<any> = () => {
const [count, setCount ] = useState<number>(0)
return (
<div>
<Button type='primary' onClick={() => setCount(count + 1)}>加1</Button>
<CountContext.Provider value={count}>
<Test1 />
</CountContext.Provider>
</div>
);
};
const Test1: React.FC<any> = () => {
const count = useContext(CountContext)
return <div style={{marginTop: 20}}>
子组件获取到的count: {count}
<Test2 />
</div>
}
const Test2: React.FC<any> = () => {
const count = useContext(CountContext)
return <div style={{marginTop: 20}}>
孙组件获取到的count: {count}
</div>
}
export default Mock;
useReducer
- 设置统一状态
- 接收两个参数,分别为 state action,然后返回一个状态的 count(属性值) 和 dispatch(方法)
作用
作用类似于 redux, 增强函数体
代码演示
详细代码
import React, { useState, useReducer } from 'react';
import { Button } from 'antd';
const Mock: React.FC<any> = () => {
const [count, dispatch] = useReducer((state:any, action: any)=> {
switch(action?.type){
case 'add':
return state + action?.payload;
case 'sub':
return state - action?.payload;
default:
return state;
}
}, 0);
return (
<div>
<div style={{display: 'flex', justifyContent: 'flex-start'}}>
<Button type='primary' onClick={() => dispatch({type: 'add', payload: 1})}>加1</Button>
<Button type='primary' onClick={() => dispatch({type: 'sub', payload: 1})} style={{marginLeft: 24}}>减1</Button>
</div>
<div style={{marginTop: 20}}>count: {count}</div>
</div>
);
};
export default Mock;
useMemo
- useMemo 的使用和 useEffect 的使用方式基本一致
- 当组件进行更新时,虽然子组件不会改变状态,但还是会进行刷新,而 useMemo 只监听特定的值,也就是说,当这个值没有发生变化时,不会更新
- 在 useMemo 函数内通过复杂计算获取当前值得时候,不需要再父组件每次更新的时候重新计算,只要在依赖项发生变化的时候计算即可
作用
当一个父组件中调用了一个子组件的时候,父组件的 state 发生变化,会导致父组件更新,而子组件虽然没有发生改变,但也会进行更新。useMemo 就是函数组件为了防止这种不必要的而采取的手段,所以一般 useMemo 是优化手段
代码演示
详细代码
import React, { useState, useMemo } from 'react';
import { Button } from 'antd';
const Mock: React.FC<any> = () => {
const [count, setCount ] = useState<number>(0)
const add = useMemo(() => {
return count + 1
}, [count])
return (
<div style={{display: 'flex', justifyContent: 'space-between', paddingRight: 50}}>
<Button type='primary' onClick={() => setCount(count + 1)}>加1</Button>
<div>count: {count}</div>
<div>次数: {add}</div>
</div>
);
};
export default Mock;
useCallback
通常在将一个组件中的函数,传递给子元素进行回调使用时,使用useCallback对函数进行处理
作用
与 useMemo 方法类似,只是 useMemo 缓存的是变量, 而 useCallBack 缓存的是函数
代码演示
详细代码
import React, { useState, useCallback } from 'react';
import { Button } from 'antd';
const MockMemo: React.FC<any> = () => {
const [count,setCount] = useState(0)
const [show,setShow] = useState(true)
const add = useCallback(()=>{
setCount(count + 1)
},[count])
return (
<div>
<div style={{display: 'flex', justifyContent: 'flex-start'}}>
<TestButton title="普通点击" onClick={() => setCount(count + 1) }/>
<TestButton title="useCallback点击" onClick={add}/>
</div>
<div style={{marginTop: 20}}>count: {count}</div>
<Button onClick={() => {setShow(!show)}}> 切换</Button>
</div>
)
}
const TestButton = memo((props:any)=>{
console.log(props.title)
return <Button type='primary' onClick={props.onClick} style={props.title === 'useCallback点击' ? {
marginLeft: 20
} : undefined}>{props.title}</Button>
})
export default MockMemo;
useRef
- useRef 类似于类组件的 this
- 可以传入初始值(initialValue),并且这个对象只有一个 current属性
- useRef 不会随着渲染,生命周期而改变,这点与 createRef 有着本质区别
作用
useRef 获取当前元素的所有属性,并且返回一个可变的ref对象,并且这个对象只有current属性,可设置initialValue
代码演示
详细代码
import React, { useState, useRef } from 'react';
const Mock: React.FC<any> = () => {
const scrollRef = useRef<any>(null);
const [clientHeight, setClientHeight ] = useState<number>(0)
const [scrollTop, setScrollTop ] = useState<number>(0)
const [scrollHeight, setScrollHeight ] = useState<number>(0)
const onScroll = () => {
if(scrollRef?.current){
let clientHeight = scrollRef?.current.clientHeight; //可视区域高度
let scrollTop = scrollRef.current.scrollTop; //滚动条滚动高度
let scrollHeight = scrollRef.current.scrollHeight; //滚动内容高度
setClientHeight(clientHeight)
setScrollTop(scrollTop)
setScrollHeight(scrollHeight)
}
}
return (
<div >
<div >
<p>可视区域高度:{clientHeight}</p>
<p>滚动条滚动高度:{scrollTop}</p>
<p>滚动内容高度:{scrollHeight}</p>
</div>
<div style={{height: 200, overflowY: 'auto'}} ref={scrollRef} onScroll={onScroll} >
<div style={{height: 2000}}></div>
</div>
</div>
);
};
export default Mock;
import React, { useState, useRef, createRef } from 'react';
import { Button } from 'antd';
const CreateMock: React.FC<any> = () => {
const [renderIndex, setRenderIndex] = React.useState(1);
const refFromUseRef = React.useRef<number>();
const refFromCreateRef = createRef<any>();
if (!refFromUseRef.current) {
refFromUseRef.current = renderIndex;
}
if (!refFromCreateRef.current) {
// @ts-ignore
refFromCreateRef.current = renderIndex;
}
return <>
<p>Current render index: {renderIndex}</p>
<p>refFrom UseRef: {refFromUseRef.current}</p>
<p>refFrom CreateRef: {refFromCreateRef.current}</p>
<Button type='primary' onClick={() => setRenderIndex(renderIndex + 1)} >点击</Button>
</>
};
export default CreateMock;
useImperativeHandle
当一个页面很复杂的时候,我们会将这个页面进行模块化,这样会分成很多个模块,有的时候我们需要在最外层的组件上控制其他组件的方法,希望最外层的点击事件,同时执行子组件的点击事件,这时就需要 useImperativeHandle 的帮助
作用
可以让你在使用 ref 时自定义暴露给父组件的实例值。
代码演示
详细代码
import React, { useState, useImperativeHandle, useRef } from 'react';
import { Button } from 'antd';
const Children: React.FC<any> = ({cRef}) => {
const [count, setCount] = useState<number>(0)
const add = () => {
setCount((c) => c + 1)
}
useImperativeHandle(cRef, () => ({
add
}))
return <div style={{marginBottom: 20}}>
<p>点击次数:{count}</p>
<Button type='primary' onClick={() => add()}>加1</Button>
</div>
}
const Mock: React.FC<any> = () => {
const ref = useRef<any>(null)
return (
<div>
<Children cRef={ref} />
<Button type='primary' onClick={() => {
ref.current.add()
}}>父节点加1</Button>
</div>
);
};
export default Mock;