React Hook
hook
优缺点
- 优点:
- 写法更加简洁;
- 更容易拆分组件, 复用代码;
- 不用再去考虑 this 的指向问题;
- 缺点:
- useEffect 是响应式的, 当某个依赖项变化时它才会被调用。你必须去理解它的调用时机、调用时的状态老旧问题, 这不直观, 也难以维护。有时, useEffect 会发生比你预期更多的调用次数 解决方案: 不要在 useEffect 里面写太多的依赖项, 划分这些依赖项成多个单一功能的 useEffect。其实这点是遵循了软件设计的“单一职责模式”
- 状态不同步; 解决方案: 手动传参 或使用 useRef
hook 为什么不能在 if 判断和循环语句中?
类组件的状态是以对象形式储存的, 每个状态都有一个key和value相对应,但是在函数式组件中, useState方法只接受了状态的初始值作为参数, 并没有key, 所以, 函数式组件的状态不能以对象的形式存储, 只能以链表的形式存储,需要保持顺序, 这样才能使每次渲染的序列对得上.而函数式组件每次渲染都会重新生成一个状态序列, 如果在 if 里调用, 就可能导致某次渲染的时候状态序列有缺失, 从而出现异常。
useState
useState 可以让我们在函数组件中使用 state 状态
与类组件中 state 的区别:
- 声明:state 直接在 useState 中声明, 类组件的 state 在 constructor 构造函数中设置
- 读取:state 可以直接使用变量, 类组件需要 this.state.xx 获取
- 更新:setXx, 类组件 this.setState({xx: ..}) 与类组件中 state 相比优点: 更加简洁, 减少了 this 指向不明确的情况
useState 中的 setState 函数, 可以接受一个回调函数作为参数, 这个回调函数会接受当前 state 的值作为参数, 然后返回一个新的 state 值。
// useState 闭包陷阱
function Counter() {
const [count, setCount] = useState(0);
const handleClick = () => {
// handleClick 函数内可以访问 Counter 函数中定义的 count 和 setCount, handleClick 函数形成了一个闭包, 而闭包会缓存 count 的值
// 就导致每次 setTimeout 中拿到的 count 都是缓存过的值, 不是最新的值
// 解决方案是在 setCount 中传入一个回调函数, 回调函数中的 currentCount 变量是函数作用域内的局部变量, 不会受到外部变量的影响, 就可以拿到最新的 count
setTimeout(() => {
setCount(count + 1);
// setState 函数, 可以接受一个回调函数作为参数
setCount((currentCount) => currentCount + 1);
}, 1000);
};
const handleReset = () => {
setCount(0);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>Increment</button>
</div>
);
}
// useEffect 也存在闭包陷阱, 可以通过传入依赖项解决
useEffect
useEffect 可以让我们在函数组件中进行一些带有副作用的操作, 可以实现类组件中生命周期的功能
componentDidMount, componentDidUpdate, componentWillUnMount
可以在函数中返回一个函数,在其中清除副作用;
特性: 依赖项变化时才执行, 好处是可以减少不必要的代码开销, 坏处是不易管理依赖项, 建议遵循单一职责原则, 以职责划分多个 useEffect 函数
useContext
组件通信(非关系组件的通信,祥见 react.md), 用来处理多层级传递数据的方式, 减少组件嵌套
// 使用 createContext 建立一个 context, 并导出
import { createContext } from "react";
const MyContext = createContext();
export default MyContext;
// 传递数据组件:
import MyContext from './MyContext';
return (
// 传递数据组件里使用 Provider 包裹着子组件, 并且在用value属性来传递数据
<MyContext.Provider value={value}>
<B />
</MyContext.Provider>
);
// 获取数据组件:
// 接受数据的组件导入定义的 context 使用 Consumer 来接收
return (
<>
<MyContext.Consumer>
{(value) => <span>{value}</span>}
</MyContext.Consumer>
{/* 如果是多个 context, 需要嵌套使用 */}
<MyContext.Consumer>
{(value) => (
<>
<span>{value}</span>
<MyContext2.Consumer>
{(value2) => <span>{value2}</span>}
</MyContext2.Consumer>
</>
)}
</MyContext.Consumer>
</>
);
export default B;
// 为避免嵌套使用, 可以通过 useContext 来重新获取值
const value = useContext(MyContext);
const value2 = useContext(MyContext2);
return (
<>
<span>{value}</span>
<span>{value2}</span>
</>
);
useRef
1. useRef 可以挂载在原生元素上来获取其DOM对象本身;
const inputRef = useRef(null);
function handleClick() {
inputRef.current.focus();
}
<input ref={inputRef} />;
2. 可用于设置一个可变的引用值, 不随组件渲染变化。与 useState 不同, useRef 返回的引用对象在组件重新渲染时保持不变, 设置引用值也不会触发组件的重新渲染。
useMemo 和 React.memo 的区别
-
useMemo 缓存计算结果
- 作用: 缓存计算结果, 避免在每次渲染时都进行复杂的计算和重新创建对象
- 使用场景: 消耗量大的运算、递归之类的
const res = useMemo(() => { return 依赖项相关计算; }, [依赖项]);
- useMemo 也可以缓存组件
const SlowComponentMemo = memo(SlowComponent); function Component() { return <SlowComponentMemo />; } // 或者 function Component() { const slowComponentNode = useMemo(() => { return <SlowComponent />; }, []); return slowComponentNode; }
-
React.memo 缓存组件 react 组件的默认渲染机制是, 只要父组件重新渲染, 子组件会跟着渲染
- 作用: 用 memo 进行缓存, 允许组件在 props 没有改变的情况下跳过渲染
- 原理: 使用 memo 缓存组件之后, React 会对此组件的每一个 props 使用 Object.is() 比较新值老值, 所有 props 都返回 true 说明没有 props 更新, 则不会重新渲染
const MemoComponent = memo(fucntion SomeComponent(props) { // … })
- 由于 memo 比较 props 新老数值的原理是利用 Object.is(),就会出现一个问题: Object.is() 在判断复杂类型时, 会根据引用进行判断,而复杂类型的数据, 在函数每次执行时都会生成一个新的引用, 所以相同的数组会被判断成 false, 也就是说 props 没有变化的情况还是触发了组件的更新
- 解决方案:
- 对象或数组 通过 useMemo 缓存 --> 将对象或数组用 useMemo 计算属性来缓存(依赖项为**[]**), 这样每次父组件更新时复杂类型的数据的依赖值没变, 则会直接使用缓存, 不会重新生成新的引用
- 函数 通过 useCallback 缓存
useCallback 缓存函数
useCallback 可以缓存函数, 避免每次渲染时重新创建函数;
const handleClick = useCallback(() => {
console.log("依赖项 count 变化");
}, [count]);
useSelector
在组件多次重新渲染时, 缓存函数
useReducer
跟 react-redux 的使用方式一样, 算是提供一个简易的 Redux 版本, 用来管理相对复杂的状态数据
// 定义 reducer 函数
const reducer = (state, action) => {
// 根据不同的 action.type 执行不同的操作并返回新的 state
switch (action.type) {
case "INC":
return state + 1;
case "DEC":
return state - 1;
default:
return state;
}
};
// 在组件中调用 useReducer ,传入 reducer 函数和状态初始值
const [state, dispatch] = useReducer(reducer, 0);
// 通过 dispatch 分派一个 action 对象
dispatch({ type: "INC" });
ahooks 库
- useRequest 接口
- useControllableValue 父子组件数据共享
- useLocalStorageState 简易数据流