记录一下 React Hooks

大概不久就是各种 hooks 漫天飞舞的世界。变天了。 本文记录一下 React Hooks 学习,算是还一下技术债。只是一些形式化的理解的整理,不求甚解。 State Hooks import { useState } from 'react'; function ExampleWithManyStates() { // 声明多个 state 变量 const [age, setAge] = useState(42); const [fruit, setFruit] = useState('banana'); const [todos, setTodos] = useState([{ text: '学习 Hook' }]); const [name, setName] = useState(() => 'jeremy'); // 允许传一个函数来初始化状态 return } useState() 方法就是管理状态state的一个钩子。每次都能在库存中钩出一对值和改值器,即一个状态值,一个修改该状态值的方法。 想象一下,如果 Hooks 机制能在ExampleWithManyStates组件调用之外做好状态的持久化,那么不难理解每次update该组件(即重新调用该组件函数),状态数据能够累计或递增维持了。 有木有很像我们以前写的辛辛苦苦写的一大堆的 reducer?reducer 有一个初始状态 initState 和更改对应状态的 update方法。初始状态或者update后的状态是维护在一个全局的store中的,这个store就是起到一个全局状态持久化的作用。 将上面切片式的取状态的写法,做一下退化还原,退化成之前的 class 组件。 import { useState } from 'react'; class ExampleWithManyStates() { constructor() { // 声明一个 state 变量 this.state = { age: 42, fruit: 'banana', todos: [{ text: '学习 Hook' }] } } setAge(age) { this.setState({age: age}) } setFruit(fruit) { this.setState({fruit: fruit}) } addTodo(todo) { this.setState({todos: [...this.state.todos, todo]}) } deleteTodo(index) { const {todos} = this.state; this.setState({ todos: [...todos.slice(0, index), ...todos.slice(index+1)] }) } // .... other codes } 写类似setAge(age) {this.setState({age: age}) }这样的代码相当乏味,而且在class组件状态很多的时候,无可避免的重复着既乏味又占代码的写法(当然这种简洁的钩子写法没有之前,只会心里莫名犯嘀咕,知道哪里不对劲也使不上劲儿)。 对比新旧组件写法,不难发现2点(实际上只是一点 / ?‍♀️): 1、通过 useState() 这样的钩子,React 将这些样板代码揉进自己的框架之中,减少了用户样板代码冗余。 2、代码更紧凑了,用一个钩子方法,就将这些class实例方法全部拍平到一个函数中。 函数组件被多处复用,其状态如何持久化? 那么更多的问题来了,对于一个函数组件,若是被包裹了一次,或许不难理解在函数调用之外,用缓存这个持久化方案解决其函数内部状态问题,这个例子的这一段:手动模拟更新还原过程 写的很清楚。但是若这个组件被多处复用,又该如何做对应位置的状态持久化呢? // sorry todo useEffect 数据获取request,设置订阅(事件监听)、手动更改 React 组件中的 DOM 都属于副作用。但是不是一定所有副作用非得放在useEffect 钩子里执行,不一定,但可能不是一个好习惯~ 先不纠结什么是副作用,从形式上,可以把 useEffect Hook 看做是 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。useEffect中的方法,是在 render 完 DOM 后再执行的,好比于class组件中执行完render后再执行componentDidMount方法一样。 import React, { useEffect, useState} from 'react'; function Counter() { const [count, setCount] = useState(0); useEffect(() => { document.title = `Clicked ${count} times`; console.log('2'); return () => { // 下次组件被更新,则此尾调会在`2`之前先执行,表示一次清除动作 console.log('3'); } }); console.log('1'); return (

clicked {count} times

); } export default Counter; 以上代码更像是componentDidUpdate,因为useEffect里的方法,会在组件第一次渲染之后和每次组件更新都会执行一次。 小心设置依赖数组 很多时候,我们只需执行一次,即等同于componentDidMount;也有时候,需要有条件的执行,怎么办呢?汇总一下。 1、仅执行一次。给useEffect多传一个空数组[],比如: useEffect(() => { document.title = `Clicked ${count} times`; }, []); 2、选择性的执行。给useEffect多传一个[count],比如: useEffect(() => { document.title = `Clicked ${count} times`; }, [count]); 数组参数前面的方法,是否执行,依赖于数组的值前后两次是否变化。 3、每次刷新都执行一遍。不传任何参数,比如: useEffect(() => { document.title = `Clicked ${count} times`; }); 知道了传与不传的区别,但知道传什么似乎更重要。又有两点须知: 1、每次刷新完毕后,都会执行useEffect方法。而useEffect每次执行都会传入一个新的匿名函数,保证了函数中所需变量都是新的,不受闭包影响。所以useEffect的第一个参数方法中,直接找外部拿参数变量即可。 2、useEffect依赖数组中的值,并不会作为前面函数的参数空间。 但到底该传递什么依赖呢?插播一段 官方说明。 只有 当函数(以及它所调用的函数)不引用 props、state 以及由它们衍生而来的值时,你才能放心地把它们从依赖列表中省略。 function Counter() { const [count, setCount] = useState(0); useEffect(() => { const id = setInterval(() => { setCount(count + 1); // 这个 effect 依赖于 `count` state }, 1000); return () => clearInterval(id); }, []); // ? Bug: `count` 没有被指定为依赖 useEffect(() => { const id = setInterval(() => { setCount(count + 1); // 这个 effect 依赖于 `count` state }, 1000); return () => clearInterval(id); }, [count]); // ? Bug: useEffect 下一次执行总会先把定时器移除 useEffect(() => { const id = setInterval(() => { setCount(c => c + 1); // ✅ 在这不依赖于外部的 `count` 变量 }, 1000); return () => clearInterval(id); }, []); // ✅ 我们的 effect 不适用组件作用域中的任何变量 return

{count}

; } shouldComponentUpdate 生命周期,该如何实现呢?请看我该如何实现 shouldComponentUpdate?。 componentWillReceiveProps 生命周期,该如何实现呢? useContext 借助React.createContext 和 useContext(),我们拥有了一种 “透传” 的的能力,能将顶层的属性,一次传递到任意子层级的组件,而不需要层层接力式的传递。 这个钩子,在处理语言intl属性时,简直不能太方便。煮一个官方的栗子: const ThemeContext = React.createContext(themes.light); function App() { return ( ); } const Child = (props) => { const theme = useContext(ThemeContext); return ( ); } useReducer const [state, dispatch] = useReducer(reducer, initialArg, init); const initialState = {count: 0}; function reducer(state, action) { switch (action.type) { case 'increment': return {count: state.count + 1}; case 'decrement': return {count: state.count - 1}; default: throw new Error(); } } function Counter() { const [state, dispatch] = useReducer(reducer, initialState); return ( <> Count: {state.count} ); } useCallback function Foo(props) { const [count, setCount] = useState(0); return ; } // 优化一下 function Foo(props) { const [count, setCount] = useState(0); // 这样只创建一次回调函数 const handleVisible = useCallback(e => { props.handleVisible(false); }, []) return ; } // function Foo(props) { const [count, setCount] = useState(0); const reportCount = useCallback( count => e => { props.reportCount(count); },) return ; } useMemo useMemo 能记忆一个方法执行的结果值,假如下次刷新组件时,依赖不变,则useMemo不会执行这个方法,而是直接拿到上次记忆的值。 如果这个方法是个耗时运算,或是返回一个组件,当依赖不变,就直接拿记忆值,这样就能起到性能优化的效果。 const long_time_result = useMemo(() => { const result = long_time_function(count); return result; }, [count]) useRef 是用对象引用方式,用户代码可以用它来做一般数据的缓存。说白了还是一种持久化。 so 和useState 有什么差别呢?唯一差别是:useState多返回了一个可以刷新本组件的方法,而useRef单纯提供一个引用访问链,你组件再怎么重复刷新,这个引用链也不会断掉。 这个钩子,在组件两次刷新之间,因为会产生两帧独立的闭包,所以用它来保持数据关联性就非常合适了。 React.memo function MyComponent(props) { // render using props } function areEqual(prevProps, nextProps) { // return true if passing nextProps to render would return // the same result as passing prevProps to render, // otherwise return false } export default React.memo(MyComponent, areEqual); 总结一下 1、useState 和 useRef钩子行为相似。 2、useContext具有透传能力 3、其他钩子在于依赖。 4、捕获值的这个特性是我们写钩子最最需要注意的问题,它是函数特有的一种特性,并非函数式组件专有。函数的每一次调用,会产生一个属于那一次调用的作用域,不同的作用域之间不受影响。 其他 react-redux的钩子 状态管理方面,React 社区最有名的工具当然是 Redux。在 react-redux@7.1 中新引用了三个 API: useSelector。它有点像 connect() 函数的第一个参数 mapStateToProps,把数据从 state 中取出来; useStore 。返回 store 本身; useDispatch。返回 store.dispatch。 关于测试 觉得还是到改了一部分 hooks 写法后,在加单元测试。现在堆积了很多逻辑的 class 组件真心难写。如何测试使用了 Hook 的组件4

本文章由javascript技术分享原创和收集

发表评论 (审核通过后显示评论):