React Hooks用法
useState()
const [state, setState] = useState(initialState)
useState返回两个值,一个是带有初始值的变量,一个是更新这个变量的函数 在第一次渲染的时候,返回的状态state与传入的第一个参数值相同。 setState函数用于更新state, 当它接收一个新的 state 值的时候,会将组件的一次重新渲染加入队列, setState执行渲染,如果是对象,里面的值必须是一个新对象
useEffect();
两个参数,第一个参数是一个回调函数。在组件DOM加载完毕之后执行的函数,
可以rerurn 一个函数,这个函数是组件被重新渲染或者卸载时候执行
const App = ()=>{
useEffect(()=>{
//.....执行一些操作
return ()=>{
//这个函数是组件被重新渲染或者卸载时候执行
}
})
}
export default App;
一个函数式组件中可以存在多个 useEffect,按照顺序依次执行
用法
useEffect(() => {
console.log("修改title");
}, []);
useEffect还有第二个参数,作用是useEffect回调函数的执行受到那个状态state的影响,如果为空数组【】, 则只在组件载入时执行一次。 组件卸载时执行内部的回调函数 数组中是回调函数依赖的数据,若数据发生变化则在DOM渲染完后执行回调函数。
特殊场景Hooks
useContext()
首先创建Context
import { createContext } from "react";
const userContext = createContext({
name: "",
ID: 0,
});
export { userContext };
在根组件外面包裹userContext
import React from "react";
import ReactDOM from "react-dom/client";
import { userContext } from "./context/index";
import App from "./App";
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
root.render(
<userContext.Provider value={{ name: "赵子豪", ID: 20211544112 }}>
<React.StrictMode>
<App />
</React.StrictMode>
</user
在子组件中使用,直接读取外层传入的信息
import React, { useContext } from "react";
import { userContext } from "./context";
function App() {
const user = useContext(userContext);
return <div className="App">user:{user.name}</div>;
}
export default App;
当组件上层最近的<MyContext.Provider> 更新时 该Hooks 会触发重新渲染,并使用最新传递给<MyContext.Provider>的context 的value值
举一个例子:
import React, { useContext, useState } from "react";
import { StudentContext, userContext } from "./context";
import Student from "./components/Student";
function App() {
const [age, setage] = useState(0);
const user = useContext(userContext);
function addage() {
setage(age + 1);
}
return (
<StudentContext.Provider value={{ name: "李四", age: age }}>
<div className="App">user:{user.name}</div>
<button
onClick={() => {
addage();
}}
>
增加年龄
</button>
<Student />
</StudentContext.Provider>
);
}
export default App;
import React, { memo, useContext } from "react";
import { FC, ReactNode } from "react";
import { StudentContext } from "../../context";
interface Props {
children?: ReactNode;
}
const Student: FC<Props> = () => {
const student = useContext(StudentContext);
return <div>学生信息是:{student.age}</div>;
};
export default memo(Student);
在父组件App.tsx中向内通过Context传递了一个Student信息,并且age属性是受App.tsx中addage() 操控的, 当在App.tsx中改变age的值,子组件也重新渲染,更新age
useReduce() (了解就行,不咋用)
import React, { useReducer } from "react";
import { counterRducer } from "./Reducer";
function App() {
const [state, dispatch] = useReducer(counterRducer, { counter: 100 });
return (
<div className="App">
{state.counter}
<button onClick={() => dispatch({ type: "add" })}>+1</button>
<button onClick={() => dispatch({ type: "decrement" })}>-1</button>
</div>
);
}
export default App;
export function counterRducer(state: any, action: any) {
switch (action.type) {
case "add":
return { ...state, counter: state.counter + 1 };
case "decrement":
return { ...state, counter: state.counter - 1 };
default:
return state;
}
}
useState的替代品,useState保存的数据处理逻辑比较复杂时可以使用useReducer来将其进行拆分 或者这次修改的state需要依赖之前的state时,也可以使用
useCallback()
import React, { useState } from "react";
function App() {
const [sum, setnum] = useState(0);
function addsum() {
setnum(sum + 1);
}
return (
<div className="App">
<h1>求和:{sum}</h1>
<button onClick={addsum}>+1</button>
</div>
);
}
export default App;
这个组件每次被加载addsum都会被定义一次,每次更新addsum也会重新定义一次,就造成了内存空间中有许多个addsum,虽然函数会因为未被使用而被销毁,但是仍会有一下性能问题.
- useCallback会返回一个函数的memoized的值
- 在依赖不变的情况下,多次定义的时候,返回的值时相同的,
const addsum = useCallback(function () {
setnum(sum + 1);
}, []);
使用useCallback 将函数包裹住,第二个参数是依赖项, 当点击按钮 触发addsum时,组件重新渲染,sum变为1,但是由于依赖项为【】空,useCallback是由记忆的,addsum 不会发生变化,useCallback返回的函数还是之前的那个函数,因为闭包的原因,返回的函数内部sum还是等于0,所以在此点击按钮 触发addsum() sum的还是1, 这就是 被称为 闭包陷阱 这就造成 sum只能加一次; 接下来就是正确✔写法
const addsum = useCallback(function () {
setnum(sum + 1);
}, [sum]);
到目前为止,使用useCallback似乎并没有什么太大的价值,接下来看下面这段代码
import React, { useCallback, useState } from "react";
import Student from "./components/Student/index";
function App() {
const [sum, setnum] = useState(0);
const [message, setmessage] = useState("基础信息");
const addsum = useCallback(
function () {
setnum(sum + 1);
},
[sum]
);
/* function addsum() {
setnum(sum + 1);
} */
function changetext() {
setmessage("更改之后的信息");
}
return (
<div className="App">
<h1>求和:{sum}</h1>
<h2>{message}</h2>
<Student addsum={addsum} />
<button onClick={changetext}>更改信息</button>
<button onClick={addsum}>+1</button>
</div>
);
}
export default App;
import React, { memo } from "react";
import { FC, ReactNode } from "react";
interface Props {
children?: ReactNode;
addsum: Function;
}
const Student: FC<Props> = ({ addsum }) => {
console.log("子组件被渲染");
return (
<div>
<button
onClick={() => {
addsum();
}}
>
加数
</button>
</div>
);
};
export default memo(Student);
如果使用
function addsum() {
setnum(sum + 1);
}
这个没有被useCallback,处理的函数,则更新message时,会重新定义addsum() 函数, 因为子组件Student 传入了addsum函数,子组件会重新渲染,控制台打印"子组件被渲染" ;
若使用经过useCallback包裹的addsum函数,依赖项是sum , 则更新message时,由于useCallback的记忆功能。不会再重新定义addsum函数,子组件不会被重新渲染。 所以就防止了子组件的无效渲染,提高了性能
const addsum = useCallback(
function () {
setnum(sum + 1);
},
[sum]
);
使用useCallback和不使用useCallback 定义一个函数是不会带来性能优化的;
使用useCallback 定义的函数,传递给子组件,防止造成没必要的渲染
通常使用useCallback 的目的是不希望组组件进行多次渲染,并不是为了函数及进行缓存
闭包陷阱解决方案:
- 正确填写依赖
- 使用useRef,在组建多次渲染时返回的是同一个值
useMemo();
先看一个案例
import React, { useState } from "react";
function addsum(n:number) {
console.log("12312");
let sum = 0;
for (let i = 0; i <= n; i++) {
sum += i;
}
return sum;
}
function App() {
const [counter, setCounter] = useState(0);
function counteradd() {
return setCounter(counter + 1);
}
return (
<div className="App">
<h1>1+到5:{addsum(50)}</h1>
<h2>{counter}</h2>
<button onClick={counteradd}>+1</button>
</div>
);
}
export default App;
当点击按钮时,counter增加,会造成页面重新渲染,addsum() 会再次执行,这样就造成了一定的性能开销 useMemo登场:useMemo有两个参数,第一个参数是一个函数,第二个参数是一个数组,内部填入依赖项, useMemo和useCallback很相似,useMemo是对函数返回值做记忆,
import React, { useMemo, useState } from "react";
function addsum(n: number) {
console.log("12312");
let sum = 0;
for (let i = 0; i <= n; i++) {
sum += i;
}
return sum;
}
function App() {
const [counter, setCounter] = useState(0);
const result = useMemo(() => {
return addsum(50);
}, []);
function counteradd() {
return setCounter(counter + 1);
}
return (
<div className="App">
<h1>1+到5:{result}</h1>
<h2>{counter}</h2>
<button onClick={counteradd}>+1</button>
</div>
);
}
export default App;
这样写,当counter变化时,addsum 不会再次执行,因为,没有发生变化,不再重复执行
useCallback(fn,[]) 等价于 useMemo(()=> fn,[]);
用法:
- 对于大量进行计算的操作,是否有必要每次渲染都重新计算;
- 对于组件传递相同内容的对象时,使用useMemo 进行性能优化
useRef()
useRef() 返回一个ref对象,返回的ref对象在组件的整个生命周期保持不变; 最常用的ref时两种用法: 用法一:引入DOM元素;
import React, { useRef } from "react";
function App() {
const dom = useRef<HTMLDivElement>(null);
const input = useRef<HTMLInputElement>(null);
function showitem() {
console.log(dom.current);
input.current?.focus();
}
return (
<div ref={dom} className="App">
<input type="text" ref={input} />
<button onClick={showitem}>展示DOM</button>
</div>
);
}
export default App;
用法二:保存一个数据,这个对象在整个生命周期中可以保持不变,可以用来解决useCallback中无依赖状态下,造成的闭包陷阱问题
useImperativeHandle
不是很好理解 ,用于子组件向外暴露ref,使用useImperativeHandle 可以指定向外暴露的属性和方法
import React, { useRef, ElementRef } from "react";
import MyComponent from "./components/Student";
import type ImyComentprops from "./components/Student";
function App() {
const myRef = useRef<ElementRef<typeof ImyComentprops>>(null);
return (
<div>
<MyComponent ref={myRef} />
<button onClick={() => myRef.current?.focus()}>Focus Input</button>
<button onClick={() => myRef.current!.setValue("我更改值了")}>
改值
</button>
</div>
);
}
export default App;
import React, { forwardRef, useRef, useImperativeHandle } from "react";
//props接口定义
interface MyComponentProps {
label?: string;
onClick?: () => void;
}
//定义向外暴露的ref接口
export interface ImyComentprops {
focus: () => void;
setValue: (str: string) => void;
}
const MyComponent = forwardRef<any, MyComponentProps>((props, ref) => {
const inputref = useRef<HTMLInputElement>(null);
//向外父组件暴露 展示的ref属性值
useImperativeHandle(ref, () => {
return {
focus() {
inputref.current?.focus();
},
setValue(str: string) {
inputref.current!.value = str;
},
};
});
return (
<div>
<input ref={inputref} type="text" />
<button>Focus Input</button>
</div>
);
});
export default MyComponent;
此Hooks 不常用,感觉主要用封装组件库,不让用户随意动里面的东西
useLayoutEffect()
- useLayoutEffect() 会在渲染的内同更新到DOM之前执行,会阻塞DOM的更新;
- useEffect() 会在渲染的内容更新到DOM上之前执行,不会阻塞DOM的更新
接下来一个例子:判断值是否符合预设值,不合符则更新值;
import React, { useState, useEffect, useLayoutEffect } from "react";
function App() {
const [sum, setsum] = useState(10);
/* useEffect(() => { //使用useEffect 去更新值,会出现短暂白屏的效果
console.log("useEffect");
if (sum === 0) {
setsum(Math.random() * 10);
}
}); */
function changesum() {
setsum(0);
}
useLayoutEffect(() => { //使用useLayoutEffect() 因为是在DOM渲染之前更改的值,不会出现
//白屏现象
console.log("useLayoutEffect");
if (sum === 0) {
setsum(Math.random() * 10);
}
});
return (
<div>
<h1>随机数:{sum}</h1>
<button onClick={changesum}>改变数字</button>
</div>
);
}
export default App;
useLayoutEffect() 官方不建议使用,
会阻塞DOM的更新