HOC、Hooks、异步组件、React 18——这四个话题放在一起,因为它们代表了 React 在不同阶段对”组件逻辑复用”这个问题的答案。
高阶组件(HOC)是 Class 组件时代的解决方案。Hooks 出现后,大部分 HOC 的场景被取代了,但 HOC 的设计思想——组合优于继承——仍然是理解 React 的关键。异步组件解决的是代码体积问题,React 18 的 Concurrent Mode 则是在渲染模型层面动了刀。
高阶组件(HOC) ⾼阶组件(HOC)是 React 中⽤于复⽤组件逻辑的⼀种⾼级技巧。HOC ⾃身不是 React API 的⼀部分,它是⼀种基于 React 的组合特性⽽形成的设计模式。 简单点说,就是组件作为参数,返回值也是组件的函数,它是纯函数,不会修改传⼊的组件,也不会使⽤继承来复制其⾏为。相反,HOC 通过将组件包装在容器组件中来组成新组件。HOC 是纯函数,没有副作⽤。
什么时候用 HOC
抽取重复代码,实现组件复用
条件渲染,控制组件的渲染逻辑(如权限控制)
劫持被包装组件的生命周期,如渲染性能追踪、日志打点
两种实现方式 属性代理(Props Proxy) 通过组合方式,将组件包装在容器中,依赖父子组件的生命周期关系。可以返回无状态函数组件或有状态 class 组件。
操作 props 可以通过属性代理,拦截父组件传递过来的 props 并进行处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function HOC (WrappedComponent ) { const newProps = { type : 'HOC' } return <WrappedComponent {...props } {...newProps } /> } function HOC (WrappedComponent ) { return class extends React .Component { render () { const newProps = { type : 'HOC' } return <WrappedComponent {...this.props } {...newProps } /> } } }
抽象 state 通过属性代理无法直接操作原组件的 state,可以通过 props 和 callback 抽象 state
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 function HOC (WrappedComponent ) { return class extends React .Component { constructor (props ) { super (props) this .state = { name : '' } this .onChange = this .onChange .bind (this ) } onChange = (event ) => { this .setState ({ name : event.target .value }) } render ( ) { const newProps = { name : { value : this .state .name , onChange : this .onChange } } return <WrappedComponent {...this.props } {...newProps } /> } } } @HOC class Example extends Component { render () { return <input name ="name" {...this.props.name } /> } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import * as React from 'react' export default function HOC (WrappedComponent ) { return (props ) => ( <div > { props.isShow ? ( <WrappedComponent {...props } /> ) : ( <div > 暂无数据</div > ) } </div > ) }
1 2 3 4 5 6 7 8 9 10 11 function withBackgroundColor (WrappedComponent ) { return class extends React .Component { render () { return ( <div style ={{backgroudColor: '#ccc '}}> <WrappedComponent {...this.props } {...newProps } /> </div > ) } } }
反向继承(Inheritance Inversion) 返回一个继承了传入组件的类组件,在 render 中返回 super.render()。
反向继承允许 HOC 通过 this 访问原组件,直接读写 state、ref,劫持生命周期方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function HOC (WrappedComponent ) { const didMount = WrappedComponent .prototype .componentDidMount return class extends WrappedComponent { async componentDidMount ( ) { if (didMount) { await didMount.apply (this ) } } render () { return super .render () } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function HOC (WrappedComponent ) { const didMount = WrappedComponent .prototype .componentDidMount return class extends WrappedComponent { async componentDidMount ( ) { if (didMount) { await didMount.apply (this ) } this .setState ({number :2 }) } render () { return super .render () } } }
1 2 3 4 5 6 7 8 9 const HOC = (WrappedComponent ) => class extends WrappedComponent { render ( ) { if (this .props .isRender ) { return super .render () } else { return <div > 暂无数据</div > } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function HOC (WrappedComponent ) { return class extends WrappedComponent { render () { const tree = super .render () const newProps = {} if (tree && tree.type === 'input' ) { newProps.value = 'something here' } const props = { ...tree.props , ...newProps } const newTree = React .cloneElement (tree, props, tree.props .childern ) return newTree } } }
属性代理 vs 反向继承 属性代理从”组合”角度出发,适合从外部操作组件(操作 props、添加拦截器如条件渲染)。反向继承从”继承”角度出发,从内部操作组件(操作 state、生命周期和 render),功能更强大但侵入性也更强。
实际场景
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import React from 'react' import fetchMovieListByType from '../lib/utils' import MovieList from '../components/MoviesList' export default class PageA extends React.Component { state = { movieList : [] } async componentDidMount () { const moveList = await fetchMovieListByType ('comedy' ) this .setState ({ moveList }) } render () { return <MovieList data ={this.state.movieList} emptyTips ="暂无喜剧" /> } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import React from 'react' import fetchMovieListByType from '../lib/utils' import MovieList from '../components/MoviesList' export default class PageB extends React.Component { state = { movieList : [] } async componentDidMount () { const moveList = await fetchMovieListByType ('action' ) this .setState ({ moveList }) } render () { return <MovieList data ={this.state.movieList} emptyTips ="暂无动作片" /> } }
以上冗余代码过多,使用HOC 代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import React from 'react' const withFetchingHOC = (WrappedComponent, fetchingMethod, defaultProps ) => { return class extends React .Component { async componentDidMount () { const data = await fetchingMethod () this .setState ({data}) } render () { return ( <WrappedComponent data ={this.state.data} {...defaultProps } {...this.props } /> ) } } }
1 2 3 4 5 6 7 8 import React from 'react' import withFetchingHOC from '../hoc/withFetchingHOC' import fetchMovieListByType from '../lib/utils' import MovieList from '../components/MoviesList' const defaultProps = {emptyTips : '暂无喜剧' }export default withFetchingHOC (MovieList , fetchMovieListByType ('comedy' ), defaultProps)
1 2 3 4 5 6 7 8 import React from 'react' import withFetchingHOC from '../hoc/withFetchingHOC' import fetchMovieListByType from '../lib/utils' import MovieList from '../components/MoviesList' const defaultProps = {...}export default withFetchingHOC (MovieList , fetchMovieListByType ('some-other-type' ), defaultProps)
更符合 ⾥⽒代换原则(Liskov Substitution Principle LSP),任何基类可以出现的地⽅,⼦类⼀定可以出现。LSP是继承复⽤的基⽯,只有当衍⽣类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复⽤,⽽衍⽣类也能够在基类的基础上增加新的⾏为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import React from 'react' import { whiteListAuth } from '../lib/utils' function AuthWrapper (WrappedComponent ) { return class AuthWrappedComponent extends React.Component { constructor (props ) { super (props) this .state = { premissionDeied : -1 } } async componentDidMount () { try { await whiteListAuth () this .setState ({ premissionDeied : 0 }) } catch (e) { this .setState ({ premissionDeied : -1 }) } } render () { if (this .state .premissionDeied === -1 ) { return null } return <WrappedComponent {...this.props } /> } } }
组件渲染性能(反向继承) 如何计算一个组件 render 期间的渲染耗时
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 import React from 'react' class Home extends React.Component { render () { return <h1 > Hello World.</h1 > } } function withTiming (WrappedComponent ) { let start, end return class extends WrappedComponent { constructor (props ) { super (props) start = 0 end = 0 } componentWillMount () { if (super .componentWillMount ) { super .componentWillMount () } start = +Date .now () } componentDidMount () { if (super .componentDidMount ) { super .componentDidMount () } end = +Date .now () console .log (`${WrappedComponent.name} 组件渲染时间为${end - start} ms` ) } render () { return super .render () } } } export default withTiming (Home )
Hooks 详解 Hooks 是 React 16.8 引入的,目的是让函数组件也能拥有状态和副作用处理能力——之前这些能力只有 class 组件才有。
Hooks 解决的三个核心问题:
在组件之间复用状态逻辑——无需修改组件结构
将组件中相互关联的部分拆分成更小的函数,而非散落在各个生命周期中
避免 class 组件的 this 绑定问题和不必要的优化点
常见 Hooks useState 6 1 const [state, setState] = useState(initialState);
setState ⽀持函数式组件有⾃⼰的 state;
⼊参:具体值或⼀个函数;
返回值:数组,第⼀项是 state 值,第⼆项负责派发数据更新,组件渲染;
注意:setState 会让组件重新执⾏,所以⼀般需要配合 useMemo 或 useCallback;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 const DemoState = (props ) => { const [number, setNumber] = useState (0 ) return ( <div > <span > {number}</span > <button onClick ={() => { setNumber(number + 1) console.log(number) // 0 }} /> </div > ) } const a = 1 const DemoState = (props ) => { const [number, setNumber] = useState (() => { return a === 1 ? 1 : 2 }) return ( <div > <span > {number}</span > <button onClick ={() => { setNumber(number + 1) }} /> </div > ) }
useEffect
使⽤条件:当组件init、dom render完成、操纵dom、请求数据(如componentDidMount)等;
不限制条件,组件每次更新都会触发 useEffect –> componentDidUpdate 与 componentwillreceiveprops;
useEffect 第⼀个参数为处理事件,第⼆个参数接收数组,为限定条件,当数组变化时触发事件,为[]只在组件初始化时触发;
useEffect 第⼀个参数有返回时,⼀般⽤来消除副作⽤(如去除定时器、事件绑定等);
注意:useEffect⽆法直接使⽤async await
1 2 3 4 5 6 7 8 9 useEffect (() => { const fetchData = async ( ) => { const response = await fetch ('https://xxx.com' ) const json = response.json () setData (json) } fetchData ().catch (console .error ) }, [])
useLayoutEffect 渲染更新之前的 useEffect useEffect: 组件更新挂载完成 -> 浏览器dom 绘制完成 -> 执⾏useEffect回调 ; useLayoutEffect : 组件更新挂载完成 -> 执⾏useLayoutEffect回调-> 浏览器dom 绘制完成;
渲染组件 1. useEffect:闪动; 2. useLayoutEffect:卡顿;
1 2 3 4 5 6 7 8 9 10 11 12 13 const DemoUseLayoutEffect = ( ) => { const target = useRef () useLayoutEffect (() => { const { x, y } = getPosition () animate (target.current , {x, y}) }, []) return ( <div > <span ref ={target} className ="animate" > </span > </div > ) }
useRef ⽤来获取元素、缓存数据; ⼊参可以作为初始值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const DemoUseRef = ( ) => { const dom = useRef (null ) const handerSubmit = ( ) => { console .log (dom.current ) } return ( <div > <div ref ={dom} > 表单组件</div > <button onClick ={() => handerSubmit()}>提交</button > </div > ) } const currentRef = useRef (InitialData )currentRef.current = newValue
useContext ⽤来获取⽗级组件传递过来的 context 值,这个当前值就是最近的⽗级组件 Provider 的 value; 从 parent comp 获取 ctx ⽅式; 1. useContext(Context); 2. Context.Consumer;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 const DemoContext = ( ) => { return ( <Context.Consumer > {(value) => <div > my name is {value.name}</div > } </Context.Consumer > ) } const DemoUseContext = ( ) => { const value = useContext (Context ) return <div > my name is {value.name}</div > } export default () => { return ( <div > <Context.Provider value ={{name: 'aaa '}}> <DemoContext /> <DemoUseContext /> </Context.Provider > </div > ) }
useReducer ⼊参:
第⼀个为函数,可以视为 reducer,包括 state 和 action,返回值为根据 action 的不同⽽改变后的 state;
第⼆个为 state 的初始值;
出参: 4. 第⼀个更新后的 state 值; 5. 第⼆个是派发更新的 dispatch 函数;执⾏ dispatch 会导致组件 re-render;(另⼀个是useState)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 const DemoUseReducer = ( ) => { const [number, dispatchNumber] = useReducer ((state, action ) => { const { payload, name } = action switch (name) { case 'a' : return state + 1 case 'b' : return state - 1 case 'c' : return payload } return state }, 0 ) return ( <div > {/* 派发更新 */} <button onClick ={() => dispatchNumber({name: 'a'})}>增加</button > <button onClick ={() => dispatchNumber({name: 'b'})}>减少</button > <button onClick ={() => dispatchNumber({name: 'c', payload: 666})}>赋值</button > {/* 把 dispatch 和 state 传递给子组件 */} <MyChildren dispatch ={dispatchNumber} state ={{number}} /> </div > ) }
业务中经常将 useReducer + useContext 代替 Redux
useMemo ⽤来根据 useMemo 的第⼆个参数 deps(数组)判定是否满⾜当前的限定条件来决定是否执⾏第⼀个 cb;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 useMemo (() => ( <div > { selectList.map((i, v) => ( <span key ={v} className ={style.listSpan} > {i.patenName}</span > )) } </div > ), [selectList]) const DemoUserMemo = ( ) => { const newLog = useMemo (() => { const log = ( ) => { console .log (123 ) } return log }, []) return <div onClick ={() => newLog()}></div > }
useCallback useMemo 返回 cb 的运⾏结果; useCallback 返回 cb 的函数;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import React , { useState, useCallback } from 'react' function Button (props ) { const { handleClick, children } = props console .log ('Button -> render' ) return <button onClick ={handleClick} > {children}</button > } const MemoizedButton = React .memo (Button )export default function Index ( ) { const [clickCount, increaseCount] = useState (0 ) const handleClick = useCallback (() => { console .log ('handleClick' ) increaseCount (clickCount + 1 ) }, []) return ( <div > <p > {clickCount}</p > <MemoizedButton handleClick ={handleClick()} > click</MemoizedButton > </div > ) }
Hooks 实战经验 所有依赖都必须放进依赖数组吗? 默认的 eslint 规则要求 useEffect 中用到的外部变量都放进依赖数组。实际情况是:只有当该变量变化时需要触发 useEffect 重新执行时,才需要把它放进 deps。 不是为了满足 lint 规则而放。
useCallback 不要滥用 useCallback 在大部分场景下没有性能提升,因为必须配合 React.memo 包裹子组件才能避免不必要的渲染。而且 useCallback 的依赖层层传递会让代码可读性变差。
useMemo 适度使用 在 deps 不变且涉及非简单基础类型运算时使用。简单的 a + b 不需要 useMemo——创建 memo 的内存开销可能比重新计算还大。
useState 的正确姿势
能用其他 state/props 计算出来的就不要单独声明
保证数据源唯一——同一个数据在项目中只存储在一个地方
适当合并相关联的 state,减少不必要的 re-render
自定义 Hooks 注意:⾃定义 Hooks 本质上还是实现⼀个函数,关键在于实现逻辑 一般实现效果如:
1 const [a[,b,c...] ] = useXXX(arg[,arg2, ...])
setTitle hook 6 1 2 3 4 5 export default function useTitle(title) { useEffect(() => { document.title = title }, []) }
6 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import { useState, useEffect } from 'react' export default function useScroll (scrollRef) { const [pos, setPos] = useState([0, 0]) useEffect(() => { function handleScrolle (e) { setPos([scrollRef.current.scrollerLeft, scrollRef.current.scrollTop]) } scrollRef.current.addEventListener('scroll', handleScrolle) return () => { scrollRef.current.removeEventListener('scroll', handleScrolle) } }, []) return pos }
Hooks VS HOC
Hook 最典型的就是取代掉⽣命周期中⼤多数的功能,可以把更相关的逻辑放在⼀起,⽽⾮零散在各个⽣命周期⽅法中;
⾼阶组件可以将外部的属性功能到⼀个基础 Component 中,更多作为扩展能⼒的插件(如 react-swipeable-views 中的 autoPlay ⾼阶组件,通过注⼊状态化的 props 的⽅式对组件进⾏功能扩展,⽽不是直接将代码写在主库中);
Hook 的写法可以让代码更加紧凑,更适合做 Controller 或者需要内聚的相关逻辑,⼀般与⽬标组件内强依赖,HOC更强调对原先组件能⼒的扩展;
⽬前 Hook 还处于相对早期阶段(React 16.8.0 才正式发布Hook 稳定版本),⼀些第三⽅的库可能还暂时⽆法兼容 Hook;
异步组件 随着项⽬的增⻓,代码包也会随之增⻓,尤其是在引⼊第三⽅的库的情况下,要避免因体积过⼤导致加载时间过⻓。 React16.6中,引⼊了 React.lazy 和 React.Suspense 两个API,再配合动态 import() 语法就可以实现组件代码打包分割和异步加载。
传统模式:渲染组件 -> 请求数据 -> 再渲染组件 异步模式:请求数据 -> 渲染组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import React , { lazy, Suspense } from 'react' const About = lazy (() => import ('./About' ))class App extends React.Component { render () { return ( <div > <Suspense fallback ={ <div > loading</div > }> <About /> </Suspense > </div > ) } }
前置基础
动态 import 相对于静态 import 的 import XX from XXX,动态 import 指在运⾏时加载
错误边界 React V16 中引⼊,部分 UI 的 JS 错误不会导致整个应⽤崩溃; 错误边界是⼀种 React 组件,错误边界在渲染期间、⽣命周期⽅法和整个组件树的构造函数中捕获错误,且会渲染出备⽤ UI ⽽不是崩溃的组件。
手写异步组件 Suspense 组件需要等待异步组件加载完成再渲染异步组件的内容。 1. lazy wrapper 住异步组件,React 第⼀次加载组件的时候,异步组件会发起请求,并且抛出异常,终⽌渲染; 2. Suspense ⾥有 componentDidCatch ⽣命周期函数,异步组件抛出异常会触发这个函数,然后改变状态使其渲染 fallback 参数传⼊的组件; 3. 异步组件的请求成功返回之后,Suspense 组件再次改变状态使其渲染正常⼦组件(即异步组件);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import React , { useEffect } from 'react' export function lazy (fn ) { const fecher = { status : 'pending' , result : null , promise : null , } return function MyComponent ( ) { const getDataPromise = fn () fetcher.promise = getDataPromise getDataPromise.then (() => { fecher.status = 'resolved' fetcher.result = res.default }) useEffect (() => { if (fecher.status === 'pending' ) { throw fetcher } }, []) if (fecher.status === 'resolved' ) { return fecher.result } return null } }
1 2 3 4 5 6 7 8 const About = lazy (() => new Promise (resolve => { setTimeout (() => { resolve ({ default : <div > component content</div > }) }, 1000 ) }))
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import React from 'react' export default class Suspense extends React.Component { state = { isRender : true } componentDidCatch (error, errorInfo) { this .setState ({isRender : false }) error.promise .then (() => { this .setState ({isRender : true }) }) } render () { const { fallback, children } = this .props const { isRender } = this .state return isRender ? children : fallback } }
React18 新特性 2021.11.15 React 18 升级到了beat版本,当前 17.0.2
发布节奏:
库的 Alpha 版本:当天可⽤
公开的 Beta 版:⾄少⼏个⽉
RC 版本:⾄少在 Beta 版发布后的⼏周
正式版:⾄少在 RC 版本发布之后的⼏周
主要改动包括: 1. Automatic batching(⾃动批量更新) 2. startTransition 3. ⽀持 React.lazy 的 SSR 架构 4. Concurrent Mode (并发渲染、可选)
Automatic batching
将多个状态更新合并成⼀个重新渲染以取得更好的性能的⼀种优化⽅式;
V18 前默认不 batching 的 scene:
promise;
setTimeout;
原⽣事件处理(native event handlers);
V18 所有更新自动 batching
若不想 batching
1 2 3 4 5 6 7 8 9 10 11 12 import { flushSync } from 'react-dom' function handleClick ( ) { flushSync (() => { setCounter (c => c + 1 ) }) flushSync (() => { setFlag (f => !f) }) }
startTransition
可以让我们的⻚⾯在多数据更新⾥保持响应。这个 API 通过标记某些更新为”transitions”,来提⾼⽤户交互;
实际:可以让我们的⻚⾯在展示时时刻保持re-render;
Example:我们更新 input 的 value 的同时⽤这个 value 去更新了⼀个有 30000 个 item 的 list。然⽽这种多数据更新让⻚⾯⽆法及时响应,也让⽤户输⼊或者其他⽤户交互感觉很慢。 Solution:
1 2 3 4 setInputValue (e.target .value )setContent (e.target .value )
V18 前:update 的优先级一致 V18:支持优先级手动设置
1 2 3 4 5 6 7 8 import {startTransition} from 'react' setInputValue (input)startTransition (() => { setSearchQuery (input) })
react中的 update:
Urgent updates:reflect direct interaction, like typing, clicking, pressing, and so on; ● Transition updates:transition the UI from one view to another;
误区
与setTimeout的区别 startTransition 不会被放到下⼀次 event loop,是同步⽴即执⾏的,这也就意味着,⽐ timeout update 更早,低端机体验明显;
使⽤场景
slow rendering:re-render 需要耗费⼤量的⼯作量;
slow network:需要较⻓时间等待 response 的情况;
⽀持 React.lazy 的 SSR 架构 SSR场景 react 的 SSR(server side render)
server:获取数据;
server:组装返回带有 HTML 的接⼝;
client:加载 JavaScript;
client:hydration,将客户端的JS与服务端的HTML结合;
V18前:按序执⾏;
V18:⽀持拆解应⽤为独⽴单元,不影响其他模块;正常加载界⾯
SSR问题
server:获取数据; –> 按序执⾏,必须在服务端返回所有 HTML;
client:加载 JavaScript; –> 必须JS加载完成;
client:hydration,将客户端的 JS 与服务端的 HTML 结合; –> hydrate后才能交互;
流式 HTML & 选择性 hydrate
流式HTML
client 进⾏选择性的 hydration:
JS选择性加载
hydration 之前要求交互
记录操作⾏为,并优先执⾏Urgent comp的hydration;
Concurrent Mode (并发渲染、可选) Concurrent Mode(以下简称CM)什么是 CM 和 suspense?在2019年 react conf提出了实验性的版本来⽀持CM 和 Suspense(可以理解为等待代码加载,且指定加载界⾯)
CM:
可帮助应⽤保持响应,并根据⽤户的设备性能和⽹速进⾏适当的调整。
阻塞渲染:如UI update,需要先执⾏对应视图操作,如更新DOM; solution: a. debounce:输⼊完成后响应,输⼊时不会更新; b. throttle:功率低场景卡顿; 可中断渲染(CM): a. CPU-bound update: (例如创建新的 DOM 节点和运⾏组件中的代码):中断当前渲染,切换更⾼优先级; b. IO-bound update: (例如从⽹络加载代码或数据):response前先在内存进⾏渲染;
suspense 以声明的⽅式来“等待”任何内容,包括数据
补充知识点 使用 useReducer + useContext 实现简易 Redux 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import React , { useReducer, createContext } from "react" ;const Context = createContext ();const initState = {};const reducer = (state, action ) => { switch (action.type ) { case "setState" : return { ...state, ...action.payload }; default : return state; } }; export default Context ;export const Provider = ({ children } ) => { const [state, dispatch] = useReducer (reducer, initState); return ( <Context.Provider value ={{ state , dispatch }}> {children}</Context.Provider > ); };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import React , { useContext } from "react" ;import Context from "./simpleRedux" ;export default function SimpleReduxDemo ( ) { const { state, dispatch } = useContext (Context ); return ( <div > <div > {JSON.stringify(state)}</div > <button onClick ={() => { dispatch({ type: "setState", payload: { time: Date.now() } }); }} > set Time </button > <button onClick ={() => { dispatch({ type: "setState", payload: { random: Math.random() } }); }} > set Random </button > </div > ); };
1 2 3 4 5 6 7 8 9 10 11 import { Provider } from "./simpleRedux" ;import SimpleReduxDemo from 'simpleReduxDemo' export default function App ( ) { return ( <Provider > <SimpleReduxDemo /> </Provider > ) }
useDebounceFn 用来处理防抖函数的 Hook
6 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import debounce from 'lodash/debounce' import { useEffect, useMemo, useRef } from 'react' function useDebounceFn(fn, options) { const fnRef = useRef(fn); const wait = options?.wait ?? 1000; const debounced = useMemo( () => debounce( () => fnRef.current(...args), wait, options, ), [], ); useEffect(() => () => { debounced.cancel() }, []) return { run: debounced, cancel: debounced.cancel, flush: debounced.flush, }; } export default useDebounceFn;
useThrottleFn 用来处理函数节流的 Hook
6 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import throttle from 'lodash/throttle'; import { useEffect, useMemo, useRef } from 'react'; function useThrottleFn(fn, options) { const fnRef = useRef(fn); const wait = options?.wait ?? 1000; const throttled = useMemo( () => throttle( () => fnRef.current(...args), wait, options, ), [], ); useEffect(() => () => { throttled.cancel() }, []) return { run: throttled, cancel: throttled.cancel, flush: throttled.flush, }; } export default useThrottleFn;