React Hooks
useState(状态) 🔗
它允许你向组件添加一个状态变量。
按照惯例使用数组解构来命名状态变量,例如 [something, setSomething]
。
const [n,setN] = React.useState(0)
const [user,setUser] = React.useState({name:'F'})
语法
useState
返回一个只包含两个项的数组:
- 该状态变量 当前的
state
,最初设置为你提供的 初始化state
。 set
函数,它允许你在响应交互时将state
更改为任何其他值。
注意事项:
- 调用
set
函数 不会 改变已经执行的代码中当前的state
。
function MyComponent() {
const [name, setName] = useState('Taylor');
function handleClick() {
setName('Robin');
console.log(name); // Still "Taylor"!
}
}
- 不可以局部更新,使用扩展操作符来进行合并属性。
setState(obj)
,obj
地址要变化,才会认为数据变化。
const App = ()=>{
const [user,setUser] = useState({name:'lz',age:'18'})
setUser({
...user,
name:'tm'
})
}
useState
接受函数,便于计算处理。但为了避免重复创建初始状态,你可以将它作为初始化函数传递给 useState
,但如果初始化需要经过大量计算来初始化状态,则可以使用函数来初始化状态。
const [state,setState] = useState(()=>{
return initialState
}) // 该函数返回初始state,且只执行一次
// 惰性初始化(适用于计算成本较高的初始状态)
const [data, setData] = useState(() => {
return computeInitialData();
});
setState
接受函数 setN(i=>i+1)
,,例子中目标是使得x点击后加而,使用两次 set(x+1)
的结果是1,这是因为每次 setX(x+1)
返回的是一个新的对象并没有改变x的值。
//例子
const App = () => {
const [x, setX] = useState(0)
const click = () =>{
setX(x+1)
setX(x+1)
// setX(i => i + 1)
// setX(i => i + 1)
}
return (
<>
<h1>{x}</h1>
<button onClick={click}>add + 2</button>
</>
)
}
特性与行为
- 初始状态
initialState
可以是一个值或一个函数(惰性初始化)
- 状态更新
- 调用
setState
会触发组件重新渲染。 - 如果状态是对象或数组,需要手动合并新旧状态。
- 调用
- 异步更新
setState
是异步的,React 会批量处理状态更新以提高性能。- 如果需要基于当前状态更新,应使用函数式更新。
- 不可变性
- React 状态是不可变的,不能直接修改状态值。
- 必须通过
setState
创建新的状态对象或值。
使用场景
- 简单状态管理:如计数器、开关状态(布尔值)。
- 复杂状态管理:如表单数据、列表项、对象状态。
- 动态交互:根据用户输入或事件更新 UI。
useEffect(副作用) 🔗
语法
useEffect
是在浏览器渲染完成后执行,即,,可以理解为afterRender
,。
useEffect(() => {
// 执行副作用逻辑
return () => {
// 清理逻辑(可选)
};
}, [dependencies]); // 依赖数组(可选)
如果有多个useEffect
,会按照
特性与行为
默认行为
useEffect
默认会在每次渲染后执行。jsxuseEffect(() => { console.log("组件渲染后执行"); });
[]
空数组 如果依赖数组为空,useEffect
只会在组件首次渲染时执行一次,相当于类组件中的componentDidMount
jsxuseEffect(() => { console.log("组件首次渲染"); }, []);
[n]
依赖数组如果提供了依赖数组,
useEffect
会在依赖值发生变化时重新执行。jsxconst [count, setCount] = useState(0); useEffect(() => { console.log(`count 更新为 ${count}`); }, [count]);
清除函数
useEffect
可以返回一个清理函数,在组件卸载或下一次副作用执行前运行。jsxuseEffect(() => { const intervalId = setInterval(() => { console.log("定时器运行中..."); }, 1000); return () => { clearInterval(intervalId); // 清理定时器 console.log("定时器已清理"); }; }, []);
使用场景
- 数据获取(组件加载时从API获取)
- 订阅事件(监听浏览器事件或其他外部事件)
- 手动DOM操作
- 定时器设置/清除
模拟生命周期
componentDidMount
,即useEffect(()=>{},[])
componentDidUpdate
,即useEffect(()=>{})
默认是所有的state
变化,可以指定useEffect(()=>{},[n])
componentWillUnmount
,即useEffect(()=>{return ()=>console.log('xxx') })
举例若是设置了一个定时器可以通过return的时候销毁定时器。
useLayoutEffect 🔗
useLayoutEffect
在浏览器渲染前执行(比 useEffect
先执行),useLayoutEffect
里的任务最好影响了 Layout
,为了用户体验,优先使用 useEffect
。
使用场景
在浏览器重新绘制屏幕前计算布局。
function App() {
const [n,seN] = useState(0)
const time = useRef(null)
const onclick = ()=>{
seN(n+1)
time.current = performance.now()
}
useLayoutEffect(()=>{
if(time.current){
console.log(performance.now()-time.current)
}
})
return(<div>{n}<button onClick={onclick}>+1</button></div>)
}
ReactDOM.render(<><App/> </>, document.getElementById('root'));
useContext(上下文)
全局变量是全局的上下文。上下文是局部的全局变量(模块内值的改变其他模块是不知道的,通过重新渲染得到),更新是逐上而下的。
useContext
可以让你读取和订阅组件中的 context
。
const context = React.createContext(null) //创建上下文
function App() {
const [x, setX] = useState(0)
return (
<context.Provider value={{x, setX}}> //圈定作用域
<Father/>
</context.Provider>
)
}
function Father() {
const {x,setX} = useContext(context)//作用域内使用上下文
return (<><p>我是father{x}</p><Child/></>)
}
function Child() {
const {x,setX} = useContext(context)
return (<>我是儿子{x}<button onClick={()=>setX(x+1)}>+1</button></>)
}
ReactDOM.render(<><App/></>,document.getElementById('root');
const Context = React.createContext(null);
export default function App() {
const [x, setX] = useState(0);
return (
<Context.Provider value={{ x, setX }}>
<Father />
</Context.Provider>
);
}
const Father = () => {
const { x, setX } = useContext(Context); //作用域内使用上下文
return (
<>
<p>我是father{x}</p>
<Child />
</>
);
};
const Child = () => {
return (
<Context.Consumer>
{(value) => (
<>
我是儿子{value.x}
<button onClick={() => value.setX(value.x - 1)}>-1</button>
</>
)}
</Context.Consumer>
);
};
React.createContext()
const MyContent = React.creatContext(defalutValue)
创建一个Context
对象。当React渲染一个Context
对象时会读取组件中提供Provide
中的context
值,若是无法匹配则使用defalutValue
的默认值。
useReducer(Redux)
const [state, dispatch] = useReducer(reducer, initialArg, init);
步骤:
- 创建初始值
initialState
- 创建所有操作
reuder(state,action)
- 传给
useReducer
,得到读写的API - 调用写
({type:'操作类型')
useReducer
相当与useState
的升级版
import {useReducer} from 'react'
const initialState = {x:0};
const reducer=(state,action)=>{
if(action.type === 'add'){
return {x:state.x + action.number}
}else if(action.type === 'multi'){
return {x:state.x * (action.number)}
}
}
function App() {
const [state,dispatch] = useReducer(reducer,initialState)
const onClick1=()=>{
dispatch({type:'add',number:1})
}
const onClick2=()=>{
dispatch({type:'multi',number:2})
}
return (
<>
{state.x}
<button onClick={onClick1}>+1</button>
<button onClick={onClick2}>*2</button>
</>
);
}
export default App;
//例子二
import {useReducer} from 'react'
const initialState = {
name: 'lz',
age: '13',
gender: 'male'
};
const reducer = (state, action) => {
switch (action.type) {
case 'update':
return {...state, ...action.state}
case 'reset':
return initialState
default :
throw new Error('错误')
}
}
function App() {
const [state, dispatch] = useReducer(reducer, initialState)
const onSubmit = (e) => {
e.preventDefault()
}
const onReset = () => {
dispatch({type: 'reset'})
}
return (
<>
<form onSubmit={onSubmit} onReset={onReset}>
<div>
<label> name:
<input value={state.name} onChange={e =>
dispatch({type: 'update', state: {name: e.target.value}})}/>
</label>
</div>
<div>
<label> age:
<input value={state.age} onChange={e =>
dispatch({type: 'update', state: {age: e.target.value}})}/>
</label>
</div>
<div>
<label> gender:
<input value={state.gender} onChange={e =>
dispatch({type: 'update', state: {gender: e.target.value}})}/>
</label>
</div>
<button type='submit'>onSubmit</button>
<button type='reset'>onReset</button>
</form>
</>
);
}
export default App;
用useReducer
代替Redux
步骤:
- 将数据集中在一个
store
对象 - 将所有操作集中在
reducer
- 创建一个
Context
- 创建对数据的读写API
- 将第四步的内容放到第三步
- 用
Context.Provider
将Context
提供给所有组件 - 各个组件用
useContext
获取读写API
React.meno
React默认会有多余的reder,但props不变就没必要执行一个函数组件。下面的例子中点击后n变化了,m没有变化但是也重新渲染了,为了提高组件性能可以使用React.memo
将组件包裹起来,可检查props
变更,若是不变化则。默认浅层对比,可传入第二个参数来实现。
function App() {
const [n, setN] = React.useState(0);
const [m, setM] = React.useState(0);
const onClick = () => {
setN(n + 1);
};
return (
<div className="App">
<div>
<button onClick={onClick}>update n {n}</button>
</div>
<Child2 data={m}/>
{/* <Child data={m}/> */}
</div>
);
}
function Child(props) {
console.log("child 执行了");
console.log('假设这里有大量代码')
return <div>child: {props.data}</div>;
}
const Child2 = React.memo(props=> {
console.log("child 执行了");
console.log('假设这里有大量代码')
return <div>child: {props.data}</div>;
})
但有个bug,如果有函数的话,这个组件就会重新render
,因为两个新旧函数的地址不一样,使用useMemo
可解决这个问题。
function App() {
const [n, setN] = React.useState(0);
const [m, setM] = React.useState(0);
const onClick = () => {setN(n + 1);};
const onClickChild = () => {console.log(m);};
return (
<div className="App">
<div>
<button onClick={onClick}>update n {n}</button>
</div>
<Child2 data={m} onClick={onClickChild} />
{/* Child2 居然又执行了 */}
</div>
);
}
function Child(props) {
console.log("child 执行了");
console.log("假设这里有大量代码");
return <div onClick={props.onClick}>child: {props.data}</div>;
}
const Child2 = React.memo(Child);
userMemo
第一个参数必须()=>value
,第二个参数为依赖。当依赖发生调用的时候,才会计算出新的value。依赖不变使用旧值。(类似与Vue2的computed)
参照上个例子可以添加一行,当n更新时不会打印出Child组件的内容。
const onClickChild = useMemo(
()=>{return ()=>console.log(m)}
,[m])
useCallback
相当于一个语法糖useCallback(x=>console.log(x),[m])
等价于useMemo(()=>x=>console.log(x),[m])
上面的代码可以改写为
const onClickChild = useCallback(()=>console.log(m),[m])
useRef(引用)
用途:引用DOM对象或普通对象。
目的是需要一个值,在组件不断,useRef能保证是。
function App() {
const [n, setN] = React.useState(0);
const [_, set_] = React.useState(null)
const count = React.useRef(0)// 初始化 {current:0}
const onClick1 = () => {
setN(n + 1);
};
const onClick2 = ()=> {
set_(Math.random())
initial.current +=1
console.log(initial.current)
}
React.useEffect(
()=>{
count.current +=1
console.log(count.current)//读取
}
)
return (
<div className="App">
<button onClick={onClick1}>update n {n}</button>
<button onClick={onClick2}>update count.current {count.current}</button>
</div>
);
}
useRef不能做到自动render
,需要自己加,监听ref,当ref.current
变化时,调用set
。
Vue3的ref
可以做到当count.value
变化时,Vue3会自动render
React.forwardRef
函数组件不支持ref,类组件支持
function App() {
const buttonRef = useRef(null);
return (
<div className="App">
<Button2 ref={buttonRef}>按钮</Button2>
</div>
);
}
const Button2 = props => {
return <button className="red" {...props} />;
};
要使用ref,需要用forwardRef
,可以把Button3
的ref传递给forwardRef
,根据参数返回一个button组件。
function App() {
const buttonRef = React.useRef(null);
return (
<div className="App">
<Button3 ref={buttonRef}>按钮</Button3>
</div>
);
}
const Button3 = React.forwardRef((props, ref) => {
return <button className="red" ref={ref} {...props} />;
});
useImperativeHandle
可以让你在使用 ref 时自定义暴露给父组件的实例值。useImperativeHandle 应当与 forwardRef 一起使用。
useImperativeHandle(ref, createHandle, [deps])
自定义Hook
function useList() {
const [list, setList] = useState(null)
useEffect(() => {
ajax("/list").then(list => {
setList(list);
})
}, [])
return {
list:list,
deleteIndex: index => {
setList(list.slice(0, index).concat(list.slice(index+1)))
}
}
}
function ajax() {
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve([
{id:"1",name:"John"},
{id:"2",name:"Tom"},
{id:"3",name:"Jim"},
{id:"4",name:'lu'}
])
})
})
}
function App() {
const {list, deleteIndex} = useList(null)
return (
<div>
<h1>List</h1>
{
list ?
<ol>
{
list.map((item, index) => (
<li key={item.id}>{item.name}
<button onClick={() => {deleteIndex(index)}}>delete</button>
</li>
)
)
}
</ol>
: "load"
}
</div>
)
}
StaleClosure
过时闭包——参考链接