React 16.8 +
Hook是 React 16.8 的新增特性,它可以让我们在不编写class的情况下使用state以及其他的React特性(比如生命周期)
我们先来思考一下class组件相对于函数式组件有什么优势?比较常见的是下面的优势:
class组件可以定义自己的state,用来保存组件自己内部的状态;
class组件有自己的生命周期,我们可以在对应的生命周期中完成自己的逻辑;
class组件可以在状态改变时只会重新执行render函数以及我们希望重新调用的生命周期函数componentDidUpdate等;
所以,在Hook出现之前,对于上面这些情况我们通常都会编写class组件。
import React, { PureComponent } from 'react';
export class ClassCounter extends PureComponent {
constructor(props) {
super(props);
this.state = {
counter: 0,
};
}
increment() {
this.setState({
counter: this.state.counter + 1,
});
}
decrement() {
this.setState({
counter: this.state.counter - 1,
});
}
render() {
const { counter } = this.state;
return (
<div>
<h2>ClassCounter 当前计数:{counter}</h2>
<button onClick={(e) => this.increment()}>+1</button>
<button onClick={(e) => this.decrement()}>-1</button>
</div>
);
}
}
export default ClassCounter;
import React, { memo, useState } from 'react';
function HookCounter() {
const [counter, setCounter] = useState(0);
return (
<div>
<h2>HookCounter 当前计数:{counter}</h2>
<button onClick={(e) => setCounter(counter + 1)}>+1</button>
<button onClick={(e) => setCounter(counter - 1)}>-1</button>
</div>
);
}
export default memo(HookCounter);
Tip:
Hook指的类似于useState、useEffect这样的函数;Hooks是对这类函数的统称。
https://zh-hans.react.dev/reference/react/useState
State Hook的API就是 useState,我们在前面己经进行了学习:
口 useState会帮助我们定义一个 state变量,useState 是一种新方法,它与 class 里面的 this.state 提供的功能完全相同。
口 useState 接受唯—个参数,在第一次组件被调用时使用来作为初始化值。
口 useState的返回值是一个数组,我们可以通过数组的解构,来完成赋值会非常方便。
解构https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment
FAQ:为什么叫 useState 而不叫 createState?
口 "create”可能不是很准确,因为 state 只在组件首次渲染的时候被创建。
口 在下一次重新渲染时,useState 返回给我们当前的 state。
如果每次都创建新的变量,它就不是“state”了。
口 这也是 Hook 的名字总是以use 开头的一个原因。
当然,我们也可以在一个组件中定义多个变量和复杂变量(数组、对象)
https://zh-hans.react.dev/reference/react/useEffect
import React, { PureComponent } from 'react';
export class App extends PureComponent {
constructor() {
super();
this.state = {
counter: 100,
};
}
componentDidMount() {
document.title = this.state.counter;
}
componentDidUpdate() {
document.title = this.state.counter;
}
render() {
const { counter } = this.state;
return (
<div>
<h2>计数:{counter}</h2>
<button onClick={(e) => this.setState({ counter: counter + 10 })}>+10</button>
</div>
);
}
}
export default App;
import React, { memo, useEffect, useState } from 'react';
const App = memo(() => {
const [counter, setCounter] = useState(100);
useEffect(() => {
// 当前传入回调函数会在组件被渲染完成后,自动执行
// 网络请求/DOM操作(修改标题)/事件监听
document.title = counter;
});
return (
<div>
<h2>计数:{counter}</h2>
<button onClick={(e) => setCounter(counter + 10)}>+10</button>
</div>
);
});
export default App;
在class组件的编写过程中,某些副作用的代码,我们需要在componentWillUnmount中进行清除:
useEffect传入的回调函数A本身可以有一个返回值,这个返回值是另外一个回调函数B:
type EffectCallBack = () => (void|(()=> void | undefined));
为什么要在 effect 中返回一个函数?
React 何时清除 effect?
使用Hook的其中一个目的就是解决class中生命周期经常将很多的逻辑放在一起的问题:
使用Effect Hook,我们可以将它们分离到不同的useEffect中:
// 负责告知react,在执行完当前组件渲染之后要执行的副作用代码
useEffect(() => {
// 1.修改document的title
document.title = counter;
console.log('修改title');
});
// 一个函数式组件中可以有多个useEffect:依次执行
// 返回值: 回调函数 => 组件被重新渲染或者组件卸载时执行
useEffect(() => {
// 2. 对redux中数据的变化监听
console.log('监听redux中的数据');
return () => {
// 2. 取消对redux中数据的变化监听
};
});
useEffect(() => {
// 3. 监听eventbus中的事件
console.log('监听eventbus中的x事件');
return () => {
// 3. 取消监听eventbus中的事件
};
});
**Hook 允许我们按照代码的用途分离它们,**而不是像生命周期函数那样:
默认情况下,useEffect的回调函数会在每次渲染时都重新执行,但是这会导致两个问题:
如何决定useEffect在什么时候应该执行和什么时候不应该执行?
案例:
import React, { memo, useEffect, useState } from 'react';
const App = memo(() => {
const [counter, setCounter] = useState(100);
// 负责告知react,在执行完当前组件渲染之后要执行的副作用代码
useEffect(() => {
document.title = counter;
console.log('修改title');
}, [counter]);
useEffect(() => {
console.log('监听redux中的数据');
return () => {};
}, []);
useEffect(() => {
console.log('监听eventbus中的x事件');
return () => {};
}, []);
useEffect(() => {
console.log('发送网络请求:从服务器获取数据');
return () => {
console.log('组件卸载时执行时,才会执行一次');
};
}, []);
return (
<div>
<h2>计数:{counter}</h2>
<button onClick={(e) => setCounter(counter + 10)}>+10</button>
</div>
);
});
但是,如果一个函数我们不希望依赖任何的内容时,也可以传入一个空的数组:[ ]
useContext: https://zh-hans.react.dev/reference/react/useContext
// 以前
import React, { memo } from 'react';
import { ThemeContext, UserContext } from './context';
const App = memo(() => {
// useContext
return (
<div>
<h1>App</h1>
<div>
<h2>之前:</h2>
<UserContext.Consumer>
{(value) => {
return (
<div>
<h3>
<ThemeContext.Consumer>
{(value) => <span style={{ color: value.color, fontSize: value.size }}> UserContext.Consumer</span>}
</ThemeContext.Consumer>
</h3>
<h4>value.name : {value.name}</h4>
<h4> value.level: {value.level} </h4>
</div>
);
}}
</UserContext.Consumer>
</div>
</div>
);
});
export default App;
const App = memo(() => {
// useContext hook
const user = useContext(UserContext);
const theme = useContext(ThemeContext);
return (
<div>
<h1>App</h1>
<div>
<h3> UserContext </h3>
<h4>user.name : {user.name}</h4>
<h4> user.level: {user.level} </h4>
<h3 style={{ color: theme.color, fontSize: theme.size }}>ThemeContext</h3>
</div>
</div>
);
});
useReducerhttps://zh-hans.react.dev/reference/react/useReducer
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { ...state, counter: state.counter + 1 };
case 'decrement':
return { ...state, counter: state.counter - 1 };
case 'add_number':
return { ...state, counter: state.counter + action.num };
case 'sub_number':
return { ...state, counter: state.counter - action.num };
default:
return state;
}
}
const App = memo(() => {
const [state, dispatch] = useReducer(reducer, { counter: 0, user: [], friends: {} });
return (
<div className="use-reducer">
<h1>App</h1>
<h2>counter 当前计数: {state.counter}</h2>
<div className="btns">
<button onClick={(e) => dispatch({ type: 'increment' })}>+1</button>
<button onClick={(e) => dispatch({ type: 'decrement' })}>-1</button>
<button onClick={(e) => dispatch({ type: 'add_number', num: 5 })}>+5</button>
<button onClick={(e) => dispatch({ type: 'sub_number', num: 5 })}> -5</button>
<button onClick={(e) => dispatch({ type: 'add_number', num: 100 })}>+100</button>
</div>
</div>
);
});
useCallback https://zh-hans.react.dev/reference/react/useCallback
// 闭包陷阱
function foo(name) {
function bar() {
console.log('name: ', name);
}
return bar;
}
const bar1 = foo('why');
bar1(); //name: why
bar1(); //name: why
const bar2 = foo('kobe');
bar2(); //name: kobe
bar1(); //name: why
useCallback实际的目的是为了进行性能的优化。
如何进行性能的优化呢?
const memoizedCallback = useCallback(() => {
console.log('memoizedCallback');
doSomething(a,b)
}, [a,b]);
案例
口 案例一:使用useCallback和不使用useCallback定义一个的数是否会带来性能的优化:
口 案例二:使用useCallback和不使用useCallback定义一个函数传递给子组件是否会带来性能的优化:
import React, { memo, useCallback, useRef, useState } from 'react';
/***
* useCallback性能优化的点
* 1.当需要将一个函数传递给子组件时,最好使用useCallback进行优化,将优化之后的函数,传递给子组件
*
*/
// props中的属性发送改变时,组件本身就会被重新渲染
const HYincrement = memo((props) => {
console.log('HYincrement被渲染');
const { increment } = props;
return (
<div>
<h2>子组件:HYincrement</h2>
<button onClick={increment}>HYincrement + 1</button>
{/* 100个子组件 */}
</div>
);
});
const App = memo(() => {
// 使用 useCallback
const [counter, setCounter] = useState(100);
const [message, setMsg] = useState('hello');
// const increment = (e) => {
// //普通函数
// setCounter(counter + 1);
// console.log('increment');
// };
// 闭包陷阱
// const increment = useCallback(() => {
// console.log('increment');
// setCounter(counter + 1);
// }, [counter]);
// 进一步优化:当counter发生改变时,也使用同一个函数
// 做法一:将counter依赖移除 -> 缺点:闭包陷阱
// 做法二:useRef ,在组件多次渲染时,返回同一个值
const counterRef = useRef();
counterRef.current = counter;
const increment = useCallback(() => {
console.log('increment');
setCounter(counterRef.current + 1);
}, []);
return (
<div>
<h1>App</h1>
<h2>计数器: {counter}</h2>
<button onClick={increment}>+1</button>
<HYincrement increment={increment} />
<h2>message:{message}</h2>
<button onClick={(e) => setMsg(Math.random())}>setMsg</button>
</div>
);
});
// // 闭包陷阱
// function foo(name) {
// function bar() {
// console.log('name: ', name);
// }
// return bar;
// }
// const bar1 = foo('why');
// bar1(); //name: why
// bar1(); //name: why
// const bar2 = foo('kobe');
// bar2(); //name: kobe
// bar1(); //name: why
export default App;
通常使用useCallback的目的是不希望子组件进行多次渲染,井不是为了函数进行缓存;
useMemo https://zh-hans.react.dev/reference/react/useMemo
useMemo实际的目的也是为了进行性能的优化。
如何进行性能的优化呢?
口 useMemo返回的也是一个 memoized(记忆的)值;
口 在依赖不变的情况下,多次定义的时候,返回的值是相同的;
const memoizedValue = useMemo(()=> computeExpensiveValue(a,b), [a,b])
案例:
import React, { memo, useMemo, useState } from 'react';
// 子组件
const HelloWorld = memo(() => {
console.log('HelloWorld 子组件');
return (
<div>
<h3>子组件 HelloWorld</h3>
</div>
);
});
function calcNumTotal(num) {
console.log('calcNumTotal计算过程~');
const arr = [...new Array(num).keys()].map((item) => item + 1);
return arr.reduce((total, prev) => {
return (total += prev);
}, 0);
}
const App = memo(() => {
const [counter, setCounter] = useState(0);
// 普通
// const total = calcNumTotal(100);
// 不依赖任何的值
const total = useMemo(() => {
return calcNumTotal(100);
}, []);
// 依赖 counter
// const total = useMemo(() => {
// return calcNumTotal(counter);
// }, [counter]);
// useMemo 和 useCallback 的对比
function fn() {}
// const increment = useCallback(fn,[])
// const increment = useCallback(() => fn, []);
// 使用useMemo对子组件渲染进行优化
// const info = {
// name: 'why',
// age: 19,
// };
const info = useMemo(() => ({ name: 'why', age: 19 }), []);
return (
<div>
<h1>useMemo</h1>
<div>
<h2>计算结果: {total}</h2>
<h2>计数器:{counter}</h2>
<button onClick={(e) => setCounter(counter + 1)}>counter+1</button>
</div>
{/* total没有依赖时不会再次渲染子组件 */}
<HelloWorld total={total} />
<HelloWorld total={total} info={info} />
</div>
);
});
export default App;
useRef https://zh-hans.react.dev/reference/react/useRef
useRef返回一个ref对象,返回的ref对象在组件的整个生命周期保持不变。
最常用的ref两种用法:
用法一:引入DOM(或者组件,但是需要是class组件)元素;
const App = memo(() => {
const titleRef = useRef();
const inputRef = useRef();
function showTitleDom() {
console.log(titleRef.current);
inputRef.current.focus();
}
return (
<div>
<h1 ref={titleRef}>App</h1>
<input type="text" ref={inputRef} />
<button onClick={showTitleDom}>查看title的dom</button>
</div>
);
});
用法二:保持一个数据,这个对象在整个生命周期中可以保存下来;
let obj = null;
const App = memo(() => {
const [counter, setCounter] = useState(0);
const objRef = useRef();
console.log(obj === objRef);
obj = objRef;
//通过useRef解决闭包陷阱
const counterRef = useRef();
counterRef.current = counter;
const increment = useCallback(() => {
// setCounter(counter + 1);
setCounter(counterRef.current + 1);
}, []);
return (
<div>
<h1>App</h1>
<h2>计数: {counter}</h2>
<button onClick={(e) => setCounter(counter + 1)}> counter + 1</button>
</div>
);
});
useImperativeHandle https://zh-hans.react.dev/reference/react/useImperativeHandle
import React, { forwardRef, memo, useImperativeHandle, useRef } from 'react';
const HelloWorld = memo(
forwardRef((props, ref) => {
const inputRef = useRef();
// 子组件对父组件传入的ref进行处理
useImperativeHandle(ref, () => {
return {
focus() {
console.log('HelloWorld focus');
inputRef.current.focus();
},
setValue(value) {
inputRef.current.value = value;
},
};
});
return (
<div>
<input type="text" ref={inputRef} />
</div>
);
}),
);
const App = memo(() => {
const titleRef = useRef();
const inputRef = useRef();
function setDom() {
console.log(titleRef.current);
// inputRef.current.focus();
// inputRef.current.value = '';
inputRef.current.setValue('hhhhh');
}
return (
<div>
<h1>useImperativeHandle</h1>
<div>
<h2 ref={titleRef}>titleRef</h2>
<HelloWorld ref={inputRef} />
<button onClick={setDom}>操作DOM</button>
</div>
</div>
);
});
export default App;
useLayoutEffect https://zh-hans.react.dev/reference/react/useLayoutEffect
Warning:
useLayoutEffect
可能会影响性能。尽可能使用useEffect
。
useLayoutEffect看起来和useEffect非常的相似,事实上他们也只有一点区别而己:
如果我们希望在某些操作发生之后再更新DOM,那么应该将这个操作放到useLayoutEffect.
官方更推荐使用useEffect而不是useLayoutEffect。
const App = memo(() => {
const [counter, setCounter] = useState(100);
// useEffect(() => {
// console.log('useEffect');
// if (counter === 0) {
// // 页面会有 0 一闪而过
// setCounter(Math.random() * 99);
// }
// });
useLayoutEffect(() => {
console.log('useLayoutEffect');
if (counter === 0) {
setCounter(Math.random() * 99);
}
}, [counter]);
console.log('app render');
return (
<div>
<h1>App</h1>
<h2>counter:{counter}</h2>
<button onClick={(e) => setCounter(0)}>setCounter</button>
</div>
);
});
function useLoginLifeTime(cname) {
useEffect(() => {
console.log(cname + '组件被创建~');
return () => {
console.log(cname + '组件被销毁!');
};
});
}
const Home = memo(() => {
useLoginLifeTime('Home');
return (
<div>
<h3>Home</h3>
</div>
);
});
const About = memo(() => {
useLoginLifeTime('About');
return (
<div>
<h3>About</h3>
</div>
);
});
const App = memo(() => {
const [isShow, setIsShow] = useState(true);
useLoginLifeTime('App');
return (
<div>
<h1>自定义Hook</h1>
<h2>App</h2>
<button onClick={(e) => setIsShow(!isShow)}>组件显示/隐藏</button>
{isShow && <Home />}
{isShow && <About />}
</div>
);
});
// index.js入口文件
import { UserContext, TokenContext } from './12_自定义hook/context';
root.render(
<UserContext.Provider value={{ name: 'coderwhy', level: 99 }}>
<TokenContext.Provider value={'tokenvalue@#$$hjfjashja'}>
<App />
</TokenContext.Provider>
</UserContext.Provider>,
);
// context
import { createContext } from 'react';
const UserContext = createContext();
const TokenContext = createContext();
export { UserContext, TokenContext };
// 抽取hook
import { useContext } from 'react';
import { TokenContext, UserContext } from '../context';
function useUserToken() {
const user = useContext(UserContext);
const token = useContext(TokenContext);
return [user, token];
}
export default useUserToken;
import React, { memo } from 'react';
import { useUserToken } from './hooks';
const Home = memo(() => {
const [user, token] = useUserToken();
return (
<div>
<h3>Home</h3>
<h4>
name:{user.name}
<br />
level:{user.level}
</h4>
<h4>{token}</h4>
</div>
);
});
const About = memo(() => {
const [user, token] = useUserToken();
return (
<div>
<h3>About</h3>
<h4>
name:{user.name}
<br />
level:{user.level}
</h4>
<h4>{token}</h4>
</div>
);
});
const App = memo(() => {
return (
<div>
<h1>自定义Hook</h1>
<h2>App</h2>
<Home />
<About />
</div>
);
});
export default App;
需求:获取滚动位置
import React, { memo, useEffect } from 'react';
import { useScrollPosition } from './hooks';
import './style.css';
const Home = memo(() => {
useEffect(() => {
function handleScroll() {
console.log(window.scrollX, window.scrollY);
}
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []);
return (
<div>
<h3>Home</h3>
</div>
);
});
const About = memo(() => {
const [scrollX, scrollY] = useScrollPosition();
return (
<div>
<h3>About</h3>
<h4>scrollX:{scrollX}</h4>
<h4>scrollY:{scrollY}</h4>
</div>
);
});
const App = memo(() => {
return (
<div className="app">
<h1>自定义Hook</h1>
<h2>App</h2>
<Home />
<About />
</div>
);
});
export default App;
import { useEffect, useState } from 'react';
function useScrollPosition() {
const [scrollX, setScrollX] = useState(0);
const [scrollY, setScrollY] = useState(0);
useEffect(() => {
function handleScroll() {
// console.log(window.scrollX, window.scrollY);
setScrollX(window.scrollX);
setScrollY(window.scrollY);
}
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []);
return [scrollX, scrollY];
}
export default useScrollPosition;
需求:localStorage数据存储
import { useEffect, useState } from 'react';
function useLocalStorage(key) {
// 1.从localstorage中获取数据,并且根据数据创建组件的state
const [data, setData] = useState(() => {
const item = localStorage.getItem(key);
if (!item) return '';
return JSON.parse(item);
});
// 2.监听data改变,一旦发生改变就存储data最新值
useEffect(() => {
localStorage.setItem(key, JSON.stringify(data));
}, [data]);
// 3. 将data, setData 返回
return [data, setData];
}
export default useLocalStorage;
npm i @reduxjs/toolkit react-redux
在之前的redux开发中,为了让组件和redux结合起来,我们使用了react-redux中的connect:
在Redux7.1开始,提供了Hook的方式,我们再也不需要编写connect以及对应的映射函数了
useSelector的作用是将state映射到组件中:
口 参数一:将state映射到需要的数据中;
口 参数二:可以进行比较来决定是否组件重新渲染;
useSelector默认会比较我们返回的两个对象是否相等;
口 如何比较呢?const refEquality = (a, b) => a === b;
口 也就是我们必须返回两个完全相等的对象才可以不引起重新渲染:
useDispatch非常简单,就是直接获取dispatch函数,之后在组件中直接使用即可;
还可以通过useStore来获取当前的store对象;
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import { addNumberAction, changeMsgAction, subNumberAction } from './store/features/counter';
const Home = memo((props) => {
const { message } = useSelector(
(state) => ({
message: state.counter.message,
}),
shallowEqual,
);
const dispatch = useDispatch();
function changeMsgHandle() {
dispatch(changeMsgAction('你好!'));
}
console.log('home render');
return (
<div>
<h2>Home : message : {message}</h2>
<button onClick={(e) => changeMsgHandle()}>change message</button>
</div>
);
});
const App = memo((props) => {
// 1. 使用useSelector将redux/store中的数据映射到组件内
const { count } = useSelector(
(state) => ({
count: state.counter.count,
}),
shallowEqual,
);
// 2.使用useDispatch直接派发action
const dispatch = useDispatch();
function addNumberHandle(num, isAdd = true) {
if (isAdd) {
dispatch(addNumberAction(num));
} else {
dispatch(subNumberAction(num));
}
}
console.log('app render');
return (
<div>
<h1>App:</h1>
<h2>count:{count}</h2>
<button onClick={(e) => addNumberHandle(1)}>+1</button>
<button onClick={(e) => addNumberHandle(3)}>+3</button>
<button onClick={(e) => addNumberHandle(5)}>+5</button>
<button onClick={(e) => addNumberHandle(2, false)}>-2</button>
<Home />
</div>
);
});
//./store/features/counter.js
import { createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'count',
initialState: {
count: 99,
message: 'hello world',
},
reducers: {
addNumberAction(state, { payload }) {
state.count = state.count + payload;
},
subNumberAction(state, { payload }) {
state.count = state.count - payload;
},
changeMsgAction(state, { payload }) {
state.message = payload;
},
},
});
export const { addNumberAction, subNumberAction, changeMsgAction } = counterSlice.actions;
export default counterSlice.reducer;
// store/index.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './features/counter';
const store = configureStore({
reducer: {
counter: counterReducer,
},
});
export default store;
//index.js
import { Provider } from 'react-redux';
import store from './13_redux中的hooks/store';
import App from './13_redux中的hooks/App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<App />
</Provider>,
);
useId https://zh-hans.react.dev/reference/react/useId
什么是Hydration?
这里我引入vite-plugin-ssr插件的官方解释。
What is Hydration?
When doing SSR our pages are rendered to HTML. But HTML alone isn’t sufficient to make a page interactive. For example, a page with zero browser-side JavaScript cannot be interactive (there are no JavaScript event handlers to react to user actions such as a click on a button).
To make our page interactive, in addition to render our page to HTML in Node.js, our UI framework (Vue/React/…) also loads and renders the page in the browser. (It creates an internal representation of the page, and then maps the internal representation to the DOM elements of the HTML we rendered in Node.js.)
This process is called hydration. Informally speaking: it makes our page interactive/alive/hydrated.
https://vite-plugin-ssr.com/hydration
在进行 SSR 时,我们的页面会呈现为 HTML.
口 但仅 HTML 不足以使页面具有交互性,例如,浏览器端 JavaScript 为零的页面不能是交互式的(没有 JavaScript 事件处理程序来响应用户操作,例如单击按钮);
口 为了使我们的页面具有交互性,除了在Nodejs 中将页面呈现为 HTML 之外,我们的UI框架(Vue/React/…)还在浏览器中加载和呈现页面。(它创建页面的内部表示,然后将内部表示映射到我们在 Nodejs 中呈现的 HTML的 DOM元素)
这个过程称为hydration。
useTransitionhttps://zh-hans.react.dev/reference/react/useTransition
npm install --save-dev @faker-js/faker
// 使用fakerjs伪造数据
import { faker } from '@faker-js/faker';
const namesArray = [...Array(10000).keys()].map((item) => faker.person.firstName());
export default namesArray;
import React, { memo, useState, useTransition } from 'react';
import namesArray from './namsArray';
const App = memo(() => {
const [showNames, setShowNames] = useState(namesArray);
const [pending, setStartTransition] = useTransition();
function inputValueChangeHandle(event) {
setStartTransition(() => {
const keyword = event.target.value;
const filterShowNames = namesArray.filter((item) => item.includes(keyword));
setShowNames(filterShowNames);
});
}
return (
<div>
<h1>App</h1>
<input type="text" onInput={inputValueChangeHandle} />
<h2>用户名列表 {pending && <span>data loading....</span>} </h2>
<ul>
{showNames.map((item, index) => (
<li key={index}>
{index}:{item}
</li>
))}
</ul>
</div>
);
});
export default App;
useDeferredValue https://zh-hans.react.dev/reference/react/useDeferredValue
const App = memo(() => {
const [showNames, setShowNames] = useState(namesArray);
const deferredShowNames = useDeferredValue(showNames);
function inputValueChangeHandle(event) {
const keyword = event.target.value;
const filterShowNames = namesArray.filter((item) => item.includes(keyword));
setShowNames(filterShowNames);
}
return (
<div>
<h1>App</h1>
<input type="text" onInput={inputValueChangeHandle} />
<h2>用户名列表 </h2>
<ul>
{deferredShowNames.map((item, index) => (
<li key={index}>
{index}:{item}
</li>
))}
</ul>
</div>
);
});