useState
- useState 返回一对值,包括值和改变该值的方法
- 参数,初始化的state
- 定义: const [state, setState] = useState(initialState)
useState使用案例
import React, { useState } from "react";
import ReactDOM from "react-dom";
function App() {
// 定义num值为0,并设置一个改变num值的方法setNum
const [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都把索引增加,可以进行区别不同的useState
let 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值的方法setNum
const [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值的方法setNum
const [num, setNum] = useState(0);
const [name, setName] = useState("state");
// useMemo用于缓存对象数据,
// 第二个参数是依赖项,控制number依赖哪个数据进行变化,
// 如果为[]就会根据第一个参数中的返回值进行变化;如果不填写就会一直变,触发父组件的name,子组件也会render
let number = useMemo(() => num, [num]);
// useCallback用于缓存函数
// 第二个参数是依赖项,控制addClick依赖哪个数据进行变化,
// 如果为[]就会根据第一个参数中的函数的返回值进行变化;如果不填写就会一直变,触发父组件的name,子组件也会render
let 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>
<input
ref={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>
<button
onClick={() => {
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>
);
}