在不编写 class 的情况下使用 state(私有状态) 以及其他的 React 特性(生命周期等)。
- 在React的函数组件中调用 Hook
- 在自定义Hook中调用其他 Hook
Hooks的串联是一个链式的数据结构,从根节点向下通过next进行串联。因此Hooks不能嵌套使用,不能在条件判断中使用,也不能在循环中使用,否则会破坏链式结构,一旦破坏,就会读写混乱。
useState
- 定义值
- 定义对象
- 定义数组
- 定义函数
import React, { useState } from 'react';
function Index() {
// 第一个值用于读取状态,第二个值用于修改状态
const [count, setCount] = useState(0);
const [obj, setObj] = useState({name: '张三'});
const [arr, setArr] = useState([1,2,3]);
const [func, setFunc] = useState(() => {
return count;
});
return (
<div className={styles.demo}>
<div>
<h2>You clicked {count} times</h2>
<button onClick={() => setCount(count + 1)}>
改变值
</button>
</div>
<div>
<h2>{obj.name}</h2>
<button onClick={() => setObj({name: 'lisi'})}>改变对象</button>
</div>
<div>
<h2>{arr}</h2>
<button onClick={() => setArr(() => {
arr.push(4);
return [...arr];
})}>改变数组</button>
</div>
<div>
<h2>{func}</h2>
<button onClick={() => setFunc(count + 1)}>
改变函数
</button>
</div>
</div>
);
}
使用注意点:
- 不支持局部更新
const [myState, setMyState] = React.useState({
name: 'guangyu',
age: 18
})
setMyState({ age: 19 }) // 其他属性丢失
setMyState({ ...myState, age: 19 }) // 新的 state 合并了旧属性
- 当需要改变值时,必须通过setState修改,或者深拷贝一个新的数组进行更改在赋值
const [todoLists, getLists] = useState([]);
const list = JSON.parse(JSON.stringify(todoLists));
list.map(item => {
if (item.title === info.title) {
item.isEdit = state;
} else {
item.isEdit = false;
}
return list;
});
getLists(list);
useEffect
-
相当于react中
componentDidMount
,componentDidUpdate
和componentWillUnmount
三个生命周期 -
副作用(DOM操作, 数据请求,组件更新)
-
useEffect为什么在组件函数内部执行?可以获取props和state,采用闭包的形式
-
无阻塞更新
-
useEffect(回调函数,数组(可不写))
-
多个useEffect()
Q: 为什么在组件挂载之后请求数据?
A: 挂载前,当数据未请求回来或报错,会阻碍页面的渲染,组件挂载之后请求能实现无阻塞更新。
当第二个参数未填时,会监听所有的状态
useEffect(() => {
console.log(count);
});
当第二个参数为空数组时,不监听任何状态。只在页面初始时,监听
useEffect(() => {
console.log(count);
}, []);
当第三个参数是某一个状态时,只监听该状态,其余不监听,实现了componentDidUpdate
useEffect(() => {
console.log(count);
}, [count]);
实现 componentWillUnmount
useEffect(() => {
console.log(count);
// 通过函数
return () => {
console.log('componentWillUnmont'); // 每次都先销毁之前的
};
}, [count]);
useRef
useRef
返回一个可变的 ref 对象,其 .current
属性被初始化为传入的参数(initialValue
)。返回的 ref 对象在组件的整个生命周期内保持不变。
- 访问DOM,获取DOM元素
- 保存变量
const inputEl = useRef('123'); // 获取Dom
const save = useRef({value: '111'}); // 保存变量
<div className={styles.ref}>
<h1>useRef</h1>
<Input
type="text"
fullWidth
ref={inputEl}
/>
<br />
<Button type="primary" onClick={() => {
// console.log('input', inputEl.current.state.value); // 获取DOM
save.current.value = inputEl.current.state.value // 保存变量
console.log('save', save);
}}>获取ref</Button>
</div>
useContext
使用上下文,让组件之间也能有局部的全局变量。(相当于有了this)
-
useContext
与createContext
(生成一个父组件容器)一起使用 -
接收一个 context 对象(
React.createContext
的返回值)并返回该 context 的当前值。 -
当前的 context 值由上层组件中距离当前组件最近的
<MyContext.Provider>
的value
prop 决定。 -
当组件上层最近的
<MyContext.Provider>
更新时,该 Hook 会触发重渲染,并使用最新传递给MyContext
provider 的 contextvalue
值。(即使祖先使用React.memo
或shouldComponentUpdate
) -
useContext
的参数必须是 context 对象本身- 正确:
useContext(MyContext)
- 错误:
useContext(MyContext.Consumer)
- 错误:
useContext(MyContext.Provider)
- 正确:
// 父组件
const MyContext = createContext();
// 子组件
const ChildContext = () => {
const count = useContext(MyContext);
return (
<h2>我是子组件{count}</h2>
);
};
{/* 父子传值, value为父组件准备传给子组件的值 */}
<MyContext.Provider value={count}>
<ChildContext />
</MyContext.Provider>
useMemo
- 与
shouldComponentUpdate
类似作用,在渲染过程中避免重复渲染的问题(提升性能) - 如果没有提供依赖项数组,
useMemo()
在每次渲染时都会计算新的值。 - 当状态或父组件传来的属性发生变化时,更新组件 (也可在子组件中控制状态是否更新)
useMomo()
是一个函数,有两个参数,第一个参数是函数,第二个参数是数组(可不写)useMomo
和useEffect
执行的时间不同,useEffect
是在componentDidMount
以后执行的,而useMemo
是在组件渲染过程中执行的
useMomo
就是用的 memoization
来提高性能的, memoization
是 JavaScript
中的一种缓存技术。
如果我们有CPU密集型操作,我们可以通过将初始操作的结果存储在缓存中来优化使用。如果操作必然会再次执行,我们将不再麻烦再次使用问的CPU,因为相同结果的结果存储在某个地方,我们只是简单的返回结果。
因此,这个是以空间换速度。使用前需要确定是否值得这么做。
当第二个参数不传时,会监听所有状态并更新
const res = useMemo(() => {
return count;
});
当第二个参数为空数组时,不管状态变不变,都不更新
const res = useMemo(() => {
return count;
}, []);
当第三个参数是某一个状态时,只更新该状态,其余不更新
const [count, setCount] = useState(0);
const [num, setNum] = useState(1);
const res = useMemo(() => {
return {
count, num
};
}, [count]);
<div className={styles.memo}>
<h1>useMemo</h1>
<h2>状态=count{res.count} --- {res.num}</h2>
<Button onClick={() => {
setCount(count + 1);
}}>change-count</Button>
<Button onClick={() => {
setNum(num + 1);
}}>change-num</Button>
</div>
虽然点击num 不更新,但在此点击count时,num的值会变成之前点击的值,这是因为num的值虽然没更新,但发生了变化,并保存在缓存中了。
若想分别控制并更新,可以分开写,并分别传入需要控制的状态数组
useCallback
- 作用: 避免组件重复渲染,提高性能(和useMemo一致)
- 可以控制组件什么时候需要更新
- 虽同样使用到缓存技术,但和
useMemo
不同的是,useCallback
缓存的是个函数,是个函数就可以执行 useCallback()
有两个参数,第一个参数是函数,第二个参数是数组(可以不写)
const [count, setCount] = useState(0);
const [num, setNum] = useState(1);
const callBack = useCallback(() => {
console.log('count', count);
return count;
}, [count]);
<div className={styles.callback}>
<h1>useCallback</h1>
<h2>callBack: {callBack()}</h2>
<h2>count状态=count: {count}</h2>
<h2>num状态=num: {num}</h2>
<Button onClick={() => {
setCount(count + 1);
}}>change-count</Button>
<Button onClick={() => {
setNum(num + 1);
}}>change-num</Button>
</div>