React Hooks
useState
很常用的一个hook,可以保存函数组件中的状态
参数
useState(val)
传进去的是初始状态的值
useState(true)
usestate(50)
useState('aaa')
…
也可以是一个函数,会自动拿到函数的返回值
useState(() => {
...
const res = a + b;
return res;
})
返回
useState()
会返回一个数组,数组的第一项是当前的初始状态,第二项是个函数,用来设置这个状态
作用:
为函数提供状态,可以为保存状态,并提供最新的值
一个简单的计数例子
import { useState } from "react"
export default function HooksUseState() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>{count}</button>
</div>
);
}
执行过程:
useState
会返回一个数组,数组的第一项是当前的状态,第二项是设置这个状态(设置一个新值,引用类型的值注意)
组件一开始加载的时候,会先把 count
的初始值展示在页面上
然后执行点击事件 onCLick
,setCount
更新了 count
count
一更新,整个函数组件就会重新执行一遍,但是 count
拿到的值是更新后的值,保存了之前的状态
特性:
- 只能在函数的主体中使用
useState
,不能在if/for 等其他函数中使用
因为react按照hooks的调用顺序识别每一个hook - 每个
useState
之间相互不影响
并且如果对个useState
一起执行的话,函数会合并更新为一次
下面是多个useState
的例子
如果 加个定时器export default function HooksUseState() { const [count, setCount] = useState(0); const [flag, setFlag] = useState(false); const [arrList, setArrList] = useState([]); const handleClick = () => { setCount(count + 1); setFlag(!flag); setArrList(['张三']); }; console.log(111); // 只会打印一次,说明函数执行了一次 return ( <div> <button onClick={handleClick}>{count}</button> <div>count: {count}</div> <div>flag: {flag ? '开' : '关'}</div> <div>arrList: {arrList}</div> </div> ); }
const handleClick = () => { setCount(count + 1); setTimeout(() => { setFlag(!flag); }); setArrList(['张三']); }; // 会执行两次,说明函数执行了两次 // setCount(count + 1) 和 setArrList(['张三']) 合并成了一次 // setArrList(['张三']) 执行后,函数又执行了一次 console.log(111);
- 设置值的时候,可以是一个函数,这样就不用依赖函数中的状态了
或者如下const handleClick1 = () => { // 还可以写成函数的形式,这样就能获取到上一次的状态,再进行操作,就不必依赖当前函数组件中的状态了 setCount1(v => { return v + 1; }); } return ( <button onClick={handleClick1}>{count1}</button> )
const handleClick1 = useCallback(() => { // setCount1(v => { // 这样写,就不用依赖函数中的状态了 // return v + 1; // }); setCount1(count + 1); // 这样写还要依赖当前函数中的状态 }, [count]); // 如果不写依赖项,count1 就一直获取到的是旧值
useEffect
作用
处理函数的副作用
函数的副作用
什么是函数的副作用,一个函数除了函数本身的功能外,其他的操作都是副作用
比如函数组件的功能就是把state
和props
的值渲染在页面上,除此之外其他的操作,比如数据请求,修改dom等都是副作用
比如下面一个函数
const [value, setValue] = useState('')
const increment = (a, b) => {
const res = a + b;
setValue(res);
return res;
};
这个函数的主要作用就是两个值的相加,其中的副作用就是 setValue(res)
参数
useEffect(cb: () => () => any, arr ?: [])
第一个参数是一个回调函数(不要写成async
函数,async
返回值是一个Promise
),返回值也是一个回调函数
第二个参数是一个可选的数组,表示依赖项,根据依赖的state
,会重新执行第一个参数的回调
使用
一个简单的例子
import { useEffect, useState } from "react";
export default function HooksUseEffect() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = count; // 副作用操作
})
return (
<div>
<button onClick={() => setCount(count+1)}>{count}</button>
</div>
);
}
其中给 document.title
赋值就是一个 dom 操作,属于副作用
执行时机
useEffect 有两个参数,第一个是个回调函数,第二个是一个依赖数组
- 不传第二个参数
useEffect
会在每次state
有变化的时候,就执行第一个参数回调函数
如下例子,会在每次count
改变的时候,执行一次useEffect
回调函数的第一个参数import { useEffect, useState } from "react"; export default function HooksUseEffect() { const [count, setCount] = useState(0); useEffect(() => { document.title = count; // 副作用操作 console.log(111); }) return ( <div> <button onClick={() => setCount(count+1)}>{count}</button> </div> ); }
- 第二个参数是一个 空数组
useEffect
只会在组件第一次加载的时候,执行一次
下面的例子,document.title
一直保持,count的初始值0useEffect(() => { document.title = count; // 副作用操作 }, [])
- 添加一个特定的依赖项
第二个参数数组中 添加一个特定的依赖向,只有依赖改变的时候,才会执行useEffect
的第一个参数
下面的例子,当触发count
改变的时候,不会改变document.title
,当触发flag
改变的时候,才会给documen.title
赋值const [count, setCount] = useState(0); const [flag, setFlag] = useState(false); useEffect(() => { document.title = count; // 副作用操作 console.log(111); }, [flag]) return ( <div> <button onClick={() => setCount(count+1)}>{count}</button> <button onClick={() => setFlag(!flag)}>{flag ? '开' : '关'}</button> </div> );
清除副作用
比如说一个定时器,在组件销毁的时候,没有去取消定时器,定时器还是存在的,还是会继续执行的,就会在成一些错误
useEffect
的第一个参数回调函数,会返回一个函数,这个函数会在组件销毁前执行,因此处理一些副作用可以在这个函数里操作
如下例子
import { useEffect, useState } from "react";
export default function HooksUseEffect() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setCount(v => v+1);
}, 1000);
return () => {
clearInterval(timer); // 清除副作用
}
}, [])
return (
<div>
<button>{count}</button>
</div>
);
}
useRef
受控和不受控组件
都是指表单组件
- 受控组件
表单控件的value值需要state
去提供,然后通过事件去修改这个state
import {useState} from 'react'; const [val, setVal] = useState(''); <input type="text" value={val} onInput={e => setVal(e.target.value)} /> <p>{val}</p>
- 不受控组件
不通过state
去管理表单组件的value
import {useRef} from 'react'; const iptRef = useRef(null); <input type="text" ref={iptRef} /> <button onClick={() => console.log(iptRef.current.value)}>点击</button>
其他组件
- 如果是类组件也可以直接拿到该组件的实例
import React, {useRef} from 'react'; class Component2 extends React.Component { state = { // state属性 message: '我是类组件', show: true } onToggle() { // 方法 const {show} = this.state; this.setState({show: !show}); } render() { if (this.state.show) { return <div>{this.state.message}</div> } return ( <div>...</div> ) } } export default function HooksRef() { const component2Ref = useRef(null); return ( <div> <Component2 ref={component2Ref} /> <button onClick={() => component2Ref.current.onToggle()}>点击</button> // 调用子组件的方法 </div> ) }
- 如果是函数组件就不能直接拿了,而且也只能拿到组件下面的某个元素,组件上的方法需要 通过
useImperativeHandle
方法想父组件传递出去
如下:import {forwardRef, useCallback, useImperativeHandle, useRef, useState} from 'react'; const Component1 = forwardRef((props, ref) => { const [show, setShow] = useState(true); const ipt = useRef(null); const onToggle = useCallback(() => setShow(v => !v), []); // 事件 useImperativeHandle(ref, () => { // 将子组件中的方法传递给父组件 return { onToggle, ipt: ipt.current } }) if (show) { return <div><input ref={ipt} type="text" placeholder="我是子组件" /></div> } return <div>...</div> }) export default function HooksRef() { const component1Ref = useRef(null); return ( <div> <Component1 ref={component1Ref} /> <button onClick={() => component1Ref.current.onToggle()}>点击</button> {/* 调用子组件的方法 */} </div> ) }
useContext
作用
跨组件传值,顶级组件 -> 孙组件,传值
用法1
创建一个createContext()
import {createContext} from 'react';
export const context = createContext();
顶级组件
createContext()
身上有两个组件,一个是提供者Provider
import { useState } from "react";
import AComponent from "./AComponent";
import { context } from "./constants";
export default function HooksContext() {
const [count, setCount] = useState(0)
return (
// 将需要提供给子孙组件的状态通过value属性传递
<context.Provider value={{count, setCount}}>
<div>
<h2>顶级组件</h2>
<div>
<button onClick={() => setCount(v => v+1)}>count: {count}</button>
</div>
<AComponent />
</div>
</context.Provider>
)
}
子孙组件
另一个是消费者Consumer
, Consumer里面有一个函数,通过函数,可以拿到顶级组件传过来的变量状态和方法
import { context } from "./constants"
export default function DComponent() {
return (
// 接收顶级组件传过来的值
<context.Consumer>
{
({count, setCount}) => {
return (
<div style={{padding: '10px', border: '1px solid black'}}>
<h2>子组件D</h2>
<button onClick={() => setCount(v => v+1)}>count: {count}</button>
</div>
)
}
}
</context.Consumer>
)
}
用法2
在顶级组件中使用是一样的
在子孙级组件中,可以通过uesContext()
拿到顶级组件传过来的值,不再需要Consumer
组件了
import { useContext } from "react"
import { context } from "./constants"
export default function DComponent() {
// 通过 useContext 拿到顶级组件传过来的值
const {count, setCount} = useContext(context);
return (
<div style={{padding: '10px', border: '1px solid black'}}>
<h2>子组件D</h2>
<button onClick={() => setCount(v => v+1)}>count: {count}</button>
</div>
)
}
memo
作用
避免不必要的更新,当父组件中有状态变化的时候,跟子组件没有关系,子组件也进行了执行
// 子组件
const Component1 = (props) => {
console.log(111);
return (
<div><div>子组件</div></div>
)
}
// 父组件
export default function HooksMemo() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(v => v+1)}>count: {count}</button>
<Component1 />
</div>
)
}
当父组件中的count
状态发生变化的时候,父组件的html部分会重新执行一次,也就是说Component1
组件明明是不需要更新的,但是更新了
可以使用memo
函数包裹子组件,当依赖的状态发生变化的时候,子组件才会重新执行
import {useState} from 'react';
const Component1 = memo((props) => {
console.log(111);
return (
<div>
<div>子组件</div>
<div>{props.flag ? 'on' : 'off'}</div>
</div>
)
})
export default function HooksMemo() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(true);
return (
<div>
<button onClick={() => setCount(v => v+1)}>count: {count}</button>
<button onClick={() => setFlag(v => !v)}>切换</button>
<Component1 flag={flag} />
</div>
)
}
当count
发生变化的时候,子组件没有更新,当子组件依赖的flag
发生变化的时候,组件才重新执行了
useCallback
作用
会将一个函数保存起来,当依赖项发生变化的时候,才会更新函数
参数
useCallback(回调函数, [依赖项])
例如:
import { useCallback, useState } from "react"
export default function HooksCallback() {
const [count, setCount] = useState(0);
const handleCountChange = useCallback(() => {
console.log(count); // 此时 一致打印的是 count的初始值 0
setCount(count + 1);
}, []) // 因为依赖项数组为空数组,表明这个函数不以来任何的状态,每次获取函数都是内存中拿到的旧函数
return (
<div>
<button onClick={handleCountChange}>count: {count}</button>
</div>
)
}
const handleCountChange = useCallback(() => {
console.log(count); // 这个时候每次执行的结果就是最新的count
setCount(count + 1);
}, [count]) // 改成这样,当count发生变化的时候,函数才会更新
useMemo
作用
当依赖的状态发生变化的时候,才会重新执行函数
参数
useMemo(回调函数, [])
例子,这是一个两个数求和的计算,当两个依赖的值其中一个发生变化的时候,才会重新计算结果
import { useMemo, useState } from "react"
export default function HooksUseMemo() {
const [val1, setVal1] = useState(0);
const [val2, setVal2] = useState(0);
const res = useMemo(() => {
return +val1 + +val2;
}, [val1, val2]) // 当依赖的变量发生变化的时候,才会重新执行第一个参数的回调函数
return (
<div>
<div>
<input
type="number"
value={val1}
onChange={(e) => setVal1(e.target.value)}
/>
<span>+</span>
<input
type="number"
value={val2}
onChange={(e) => setVal2(e.target.value)}
/>
<span>=</span>
<span>{res}</span>
</div>
</div>
)
}