Hook使用规则
Hook就是JavaScript函数,但有两个额外的规则:
- 只能在函数最外层调用Hook,不要在循环、条件判断或者子函数中调用。
- 只能在React的函数组件中调用,不能在其他JavaScript函数中调用。
- 或者在自定义Hook中
useState: React组件管理自身的数据状态
参数:useState的参数是初始化state,只在初始化中起作用,后续渲染会被忽略。
返回值:useState会返回一对值,当前状态和一个让你更新它的函数。
执行时机:初始化渲染期间,state值为初始化值。重新渲染时,是更新后最新的值。
举个例子:声明一个state变量,并实现点击按钮自加。
import React, { useState } from 'react';
function Example() {
// 声明一个新的叫做 “count” 的 state 变量
const [count, setCount] = useState<number>(0);
return (
<button onClick={() => setCount(count + 1)}>Click me</button>
);
}
useEffect & useLayoutEffect: React副作用
大多数时候应该使用useEffect,当它出问题的时候再尝试使用 useLayoutEffect。useLayoutEffect用到的场景很少,比如状态更新导致屏幕闪烁,这时可以尝试使用useLayoutEffect。
副作用:React组件中执行过数据获取、订阅或手动修改Dom,这些操作成为“副作用”。
参数:第一个参数,useEffect接受一个包含命令式、且可能有副作用代码的函数。第二个参数,是effect所依赖的值数组。
返回值:useEffect返回函数在组件卸载时执行,清除effect订阅或计时器等。
执行时机:
- 传给
useEffect的函数在浏览器完渲染后调用,在任何新的渲染前执行,执行是异步的,非阻塞的。 - 传给
useLayoutEffect的函数在浏览器渲染完成前调用,执行是同步的,阻塞的。
useEffect 举个例子:组件初始化完成获取数据,并给数据的载体赋值。
import React, { useEffect, useState } from "react";
import axios from "axios";
export default function () {
const [data, setData] = useState<string>("");
const getData = async () => {
const res: any = await axios("http://xxxx");
setData(res);
}
//初始化渲染时获取数据,第二个参数传递[],告诉effect不依赖于props或state任何值,不需要重新执行
useEffect(() => {getData()},[])
return (<>{data.msg}</>)
}
useContext: 结合React.createContext实现跨组件共享默认数据
参数:context接受一个context对象作为共享数据。当前的context值由上层组件中距离最近<MyContext.Provider> 的 value prop 决定。
返回值:context当前值。
执行时机:组件上层最近的<MyContext.Provider>更新时重新触发渲染,并使用最新的value。
举个例子:父组件穿过子组件,给孙子组件传递设定好的默认颜色值。
//Parent.tsx
import { createContext } from "react";
import SonPage from "./SonPage";
export interface ThemeValue {
foreground: string;
background: string;
}
export interface ThemeContextValue {
dark: ThemeValue;
}
const themes = {
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
//创建上下文的容器,设置共享默认数据
export const ThemeContext = createContext<ThemeValue>(themes.dark)
// 使用一个 Provider 来将当前的 theme 传递给以下的组件树。
export default function () {
return (<>
<ThemeContext.Provider value={themes.dark}>
<h2>父页面Parent.tsx</h2>
<SonPage />
</ThemeContext.Provider>
</>)
}
//SonPage.tsx
import React from "react";
import GrandsonPage from "./GrandsonPage";
// 中间的组件再也不必指明往下传递
export default function SonPage() {
return (<div><h3>子页面SonPage.tsx</h3><GrandsonPage/></div>)
}
//GrandsonPage.tsx
import React, { createContext, useContext } from "react";
import { ThemeContext } from "./Parent";
export default function GrandsonPage() {
//useContext获取context
const theme = useContext(ThemeContext);
return <div style={{ background: theme.background, color: theme.foreground }}>孙子页面GrandsonPage.tsx</div>
}
useReducer:处理复杂state对象局部数据流
参数:接收三个参数,
- 第一个参数是更新状态的回调函数updateState(updateState接收两个参数,第一个参数preState前一次计算完成后的状态,第二个参数action通过dispatch调用传递过来的参数)。
- 第二个参数stateInitValue初始化传入的参数值。
- 第三个参数stateInitCallback对初始值进行首次计算的函数(stateInitCallback接收一个对象,返回一个state对象。)
返回值:返回一个数组,索引值为0的是更新后的state,索引值为1的是dispatch对象,用来在执行 updateStateCallback 的函数。
执行时机:组件初始化时。
举个例子:reducer中逻辑更加清晰。
const ComponentB = memo(function (props: { dispatch: any }) {
const { dispatch } = props;
const dateBegin = Date.now();
while (Date.now() - dateBegin < 609) { }
console.log("render done");
return (<div onClick={() => { dispatch({ type: 'log' }) }}>wwwww</div>)
})
function reducer(state: any, action: { type: string; preload: string; }) {
switch (action.type) {
case 'update':
return action.preload;
case 'log':
console.log(`Text: ${state}`);
return state;
}
}
export default function Index() {
const [text,dispatch] = useReducer(reducer,'init')
return (<>
<input value={text} onChange={ e=>dispatch({type:'update',preload:e.target.value})}/>
<ComponentB dispatch={dispatch}/>
</>)
}
useRef && useImperativeHandle:实现父子组件通信
useRef
参数:接收一个参数为.current属性默认初始值。
返回值:返回一个可变的ref对象,在组件整个生命周期内保持不变。
useImperativeHandle
参数:
- 第一个参数定义current对象的ref
- 第二个参数是一个函数,返回值是一个ref的current对象
- 第三个参数为依赖的状态,依赖变化,会重新调用
useImperativeHandle 应当与 forwardRef 一起使用,使父组件的ref穿透过来。
举个例子:实现父组件调用子组件的方法
/* 父组件 Parent.tsx */
import React, { useRef } from "react";
import { Button } from "antd";
import TableEdit from "./component/TableEdit";
export default function () {
//此处useRef请注意,因为我的项目是ts的,后面必须要加类型约束,否则在使用的时候会报错
const tableEditRef = useRef<{ setVisibleStat: (val: boolean) => void }>(null);
const handleAdd = () => {
//就是这就是这,上面如果没有类型约束,current.setVisibleStat就会报错
tableEditRef.current!.setVisibleStat(true);
};
return (<>
<Button type="link" onClick={handleAdd}>
新增
</Button>
<TableEdit ref={tableEditRef} />
</>)
}
/* 子组件 TableEdit.tsx */
import { Modal } from "antd";
import React, { forwardRef, useImperativeHandle, useState } from "react";
function TableEdit (props:object,ref: React.Ref<unknown>) {
const [visible, setVisible] = useState<boolean>(false);
useImperativeHandle(ref, () => ({
setVisibleStat: (val: boolean) => {
setVisible(val);
}
}))
return (
<Modal
visible={visible}
onOk={() => setVisible(false)}
onCancel={() => setVisible(false)}
>
<p>内容区域</p>
</Modal>
);
}
export default forwardRef(TableEdit);
useCallback & useMemo:性能优化-缓存数据状态,依赖的数据变化,重新计算结果
参数:第一个参数为回调,第二个参数为要依赖的数据。
返回值:
useCallback计算结果是一个函数,一般用于需要缓存函数式组件来提高性能,避免不需要的state变化导致整个组件刷新;useMemo返回值为缓存计算结果的值,一般用于需要计算的场景。 useCallback举个例子:监听状态的改变,减少不必要的组件更新。一般结合React.memo进行优化。
function Button(props: { handleClick: any; children: any; }) {
const { handleClick, children } = props;
console.log('Button -> render');
return (
<button onClick={handleClick}>{children}</button>
)
}
const MemoizedButton = React.memo(Button);
export default function Index() {
const [clickCount, increaseCount] = useState(0);
const handleClick = useCallback(() => {
console.log('handleClick');
increaseCount(clickCount + 1);
}, [clickCount]);
return (
<div>
<p>{clickCount}</p>
<MemoizedButton handleClick={handleClick}>Click</MemoizedButton>
</div>
)
}
useMemo举个例子:监听状态的改变,减少不需要的函数调用。
function ComponentA({ name, children }: { name: string, children:string}) {
function changeName(name:string) {
console.log('11')
return name + '改变name的方法'
}
const otherName = useMemo(() => changeName(name),[name])
return (
<>
<div>{otherName}</div>
<div>{children}</div>
</>
)
}
export default function ReduxDemo() {
const [name, setName] = useState('名称');
const [content, setContent] = useState('内容');
return (
<>
<button onClick={() => setName(new Date().getTime()+"")}>name</button>
<button onClick={() => setContent(new Date().getTime()+"")}>content</button>
<ComponentA name={name}>{content}</ComponentA>
</>
)
}
useDebugValue:配合React 开发者工具使用
参数:第一个参数为监听的状态,第二个参数一个回调函数,将自身的value作为形参,处理后再return出去一个格式化后的值。
返回值:监听状态的值。
function useFriendStatus() {
const [isOnline, setIsOnline] = React.useState(true);
React.useEffect(() => {
const interval = setInterval(() => {
setIsOnline(isOnline => !isOnline);
}, 1000);
return () => clearInterval(interval);
}, []);
React.useDebugValue(isOnline ? "Online" : "Offline");
return isOnline;
}
export default function () {
//调用下使用useDebugValue的hook
const isOnline = useFriendStatus();
}
在开发者工具中查看效果