useState
- useState 返回一对值,包括值和改变该值的方法
- 参数,初始化的state
- 定义: const [state, setState] = useState(initialState)
useState使用案例
import React, { useState } from "react";import ReactDOM from "react-dom";function App() {// 定义num值为0,并设置一个改变num值的方法setNumconst [num, setNum] = useState(0);return (<div><p>{num}</p><button onClick={() => setNum(num + 1)}>+</button></div>);}ReactDOM.render(<App />, document.getElementById("root"));
useState源码实现
当只有调用useState一次时
let lastState;function useState(initialState) {lastState = lastState || initialState;function setState(newState) {lastState = newState;render();}return [lastState, setState];}
多次调用useState
// 实现useState, 修复使用多次useState出现的问题// 通过定义个数组对象,以及stateIndex索引,每次调用useState都把索引增加,可以进行区别不同的useStatelet lastStates = [];let stateIndex = 0;function useState(initialState) {lastStates[stateIndex] = lastStates[stateIndex] || initialState;let currentIndex = stateIndex;function setState(newState) {lastStates[currentIndex] = newState;render();stateIndex = 0;}return [lastStates[stateIndex++], setState];}
useMemo 和 useCallback
单纯使用useState存在性能问题,如果更新父组件,即使没有改动子元素的数据,子元素也会进行更新,可以使用useMemo对数据进行缓存
- useMemo用于缓存对象数据
- useCallback用于缓存函数
存在的问题
function App() {// 定义num值为0,并设置一个改变num值的方法setNumconst [num, setNum] = useState(0);const [name, setName] = useState("state");let addClick = () => setNum(num + 1);return (<div><input value={name} onChange={(event) => setName(event.target.value)} /><Child number={num} addClick={addClick} /></div>);}function Child({ number, addClick }) {// 通过input每次更新父组件name的值时,也会执行Child组件的render,如果子组件非常庞大造成性能的影响console.log("render child");return <button onClick={addClick}>{number}</button>;}
使用
为了解决父组件数据的变化,触发子组件更新的问题,可以使用useMemo缓存数据
为了解决父组件数据的变化,触发子组件更新的问题,可以使用useCallback缓存函数
function App() {// 定义num值为0,并设置一个改变num值的方法setNumconst [num, setNum] = useState(0);const [name, setName] = useState("state");// useMemo用于缓存对象数据,// 第二个参数是依赖项,控制number依赖哪个数据进行变化,// 如果为[]就会根据第一个参数中的返回值进行变化;如果不填写就会一直变,触发父组件的name,子组件也会renderlet number = useMemo(() => num, [num]);// useCallback用于缓存函数// 第二个参数是依赖项,控制addClick依赖哪个数据进行变化,// 如果为[]就会根据第一个参数中的函数的返回值进行变化;如果不填写就会一直变,触发父组件的name,子组件也会renderlet addClick = useCallback(() => setNum(num + 1), [num]);return (<div><input value={name} onChange={(event) => setName(event.target.value)} /><Child number={number} addClick={addClick} /></div>);}function Child({ number, addClick }) {// 当每次更新父组件name的值时,也会执行Child组件的render,如果子组件非常庞大造成性能的影响console.log("render child");return <button onClick={addClick}>{number}</button>;}// memo对组件的缓存Child = memo(Child);function render() {ReactDOM.render(<App />, document.getElementById("root"));}render();
源码实现
useMemo和useCallback源码的实现
let lastMemo;let lastMemoDependencies;function useMemo(callback, dependencies) {if (lastMemoDependencies) {let changed = dependencies.some((item, index) => {return item !== lastMemoDependencies[index];});if (changed) {lastMemo = callback();lastMemoDependencies = dependencies;}} else {lastMemo = callback();lastMemoDependencies = dependencies;}return lastMemo;}let lastCallback;let lastCallbackDependencies;function useCallback(callback, dependencies) {if (lastCallbackDependencies) {let changed = dependencies.some((item, index) => {return item !== lastCallbackDependencies[index];});if (changed) {lastCallback = callback;lastCallbackDependencies = dependencies;}} else {lastCallback = callback;lastCallbackDependencies = dependencies;}return lastCallback;}
useRef
- 可以通过ref使用html 结构对象
- 配合 react 做一些操作dom的行为
- 还可以方便的保存上一个的状态值
const FocusInput = () => {const [inputVal, setInputVal] = useState("hell0");const InputRef = useRef();return (<div><inputref={InputRef}value={inputVal}onChange={(e) => setInputVal(e.target.value)}/><button onClick={() => InputRef.current.focus()}>选中输入框</button></div>);};const StorePrevState = () => {const [count, setCount] = useState(0);const prev = useRef(null);return (<div><p>当前值:{count}</p><p>上个值:{prev.current}</p><buttononClick={() => {prev.current = count;setCount((count) => count + 1);}}>click add</button></div>);};
useReducer
把很多个action进行统一规划成一个状态state
useReducer接收一个reducer函数 (state, action) => newState,并返回当前的state以及与其搭配使用的dispatch方法
- 首先要定义一个reducer方法,供useReducer的第一个参数
- reducer方法中,处理不同的类型
- 通过传递不同的type值,可以进行不同结果返回
使用
function reducer(state, action) {switch (action.type) {case "increment":return state + 1;case "decrement":return state - 1;default:return state;}}function App() {let [state, dispatch] = useReducer(reducer, 0);return (<div><p>{state}</p><button onClick={() => dispatch({ type: "increment" })}>+</button><button onClick={() => dispatch({ type: "decrement" })}>-</button></div>);}function render() {ReactDOM.render(<App />, document.getElementById("root"));}render();
useReducer源码实现
// useReducer原理实现let lastState;function useReducer(reducer, initialState) {lastState = lastState || initialState;function dispatch(action) {// reducer函数执行后会返回一个新的处理过的数据lastState = reducer(lastState, action);render();}return [lastState, dispatch];}
useContext
- 可以实现跨组件层级进行数据传递
- 通过React.createContext定义一个总数据调度中心,会返回的两个对象 Provider 和 Consumer
- Provider:用于提供数据
- Consumer:用户接收数据
- 在子组件中,通过是useContext获取传递过来的数据
useContext使用
const CounterCtx = React.createContext();function App() {let [state, setState] = useState({ number: 0 });return (<CounterCtx.Provider value={{ state, setState }}><Counter /></CounterCtx.Provider>);}function Counter() {// 返回的数据是 上面Provider传递的 value对象值let { state, setState } = useContext(CounterCtx);return (<div><p>{state.number}</p><button onClick={() => setState({ number: state.number + 1 })}>+</button><button onClick={() => setState({ number: state.number - 1 })}>-</button></div>);}function render() {ReactDOM.render(<App />, document.getElementById("root"));}render();
useContext源码实现
function useContext(context) {return context._currentValue;}
useEffect
- useEffect是一个Effect hook,给函数组件增加操作副作用的能力
- 和class组件中的 componentDidMount、componentDidUpdate和componentWillUnmount具有类似用途
使用 useEffect(updateFn)
useEffect使用
import React, { useState, useEffect } from "react";import ReactDOM from "react-dom";function App() {let [num, setNum] = useState(0);let [name, setName] = useState("effect");useEffect(() => {console.log("num", num);}, [num]);return (<div><p>{name}:{num}</p><button onClick={() => setName("hello")}>change name</button><button onClick={() => setNum(num + 1)}>+</button></div>);}function render() {ReactDOM.render(<App />, document.getElementById("root"));}render();
useEffect源码实现
let lastDependencies;function useEffect(effect, dependencies) {if (lastDependencies) {// effect存在let changed = dependencies.some((item, index) => {return item !== lastDependencies[index];});if (changed) {// 通过宏任务执行,在dom渲染完成后执行setTimeout(effect);lastDependencies = dependencies;}} else {setTimeout(effect);lastDependencies = dependencies;}}
useLayoutEffect
- useLayoutEffect和useEffect使用方式一样,但是它会在所有DOM变更之后同步调用effct
- useEffect不会阻塞浏览器渲染,而useLayoutEffect 会阻塞浏览器渲染
- useEffect 会在浏览器渲染结束后执行, 而useLayoutEffect 在Dom更新完成后,浏览器绘制前执行。
useLayoutEffect执行是在浏览器渲染前执行,执行时机更早。
useEffect执行是在浏览器渲染之后执行
useLayoutEffect使用
import React, { useState, useRef, useLayoutEffect } from "react";import ReactDOM from "react-dom";function AnimationEffect() {const ref = useRef();useLayoutEffect(() => {console.log("useEffec");ref.current.style.transform = "translateX(500px)";ref.current.style.transition = "all 0.8s";});let style = {width: "100px",height: "100px",backgroundColor: "red",};console.log('render');return <div style={style} ref={ref}></div>;}function render() {ReactDOM.render(<AnimationEffect />, document.getElementById("root"));}render();
useLayoutEffect源码实现
let lastLayoutDependencies;function useLayoutEffect(effect, dependencies) {if (lastLayoutDependencies) {let changed = dependencies.some((item, index) => {return item !== lastLayoutDependencies[index];});if (changed) {// 放在微任务中执行,会在DOM渲染之前执行Promise.resolve().then(effect);lastLayoutDependencies = dependencies;}} else {Promise.resolve().then(effect);lastLayoutDependencies = dependencies;}}
useMemo和React.memo区别
- useMemo:是对数据进行缓存
- React.memo:是高级函数,用来封装纯函数,是对函数组件进行的缓存。
- 组件在相同 props 的情况下渲染相同的结果,可以将其包装在 React.memo 中调用,以此记忆组件渲染结果的方式来提高组件的性能。
const Child = ({ seconds }) => {console.log("child");return <div>child{seconds}</div>;};// React.memo可以接收第二个参数, 用来判断是否进行组件的更新const MemoChild = React.memo(Child, areEqual);function areEqual(prevProps, nextProps) {if (prevProps.seconds === nextProps.seconds) {// 上次属性和这次属性传入的值相同,则返回true,不进行更新渲染。return true;} else {// 属性不同,返回false,进行渲染更新return false;}}export default function App() {const [time, setTime] = useState(Date.now());setInterval(() => {setTime((time) => (time = Date.now()));}, 10000);return (<div className="App"><h1>Hello React.memo</h1><h2>{time}</h2>{/* 如果直接使用 Child 则每次都会渲染*/}{/* <Child /> */}<MemoChild seconds={time} /></div>);}
