Category: React

  • React 性能优化

    useCallback 场景一: 避免子组件的重复渲染 当我们需要从子组件传递消息时,通常使用的是由父组件传递方法给子组件,子组件调用方法时将消息当做参数传递给这个方法。 但在有些情况我们想在重新渲染父组件时并不需要重新渲染这个子组件(子组件渲染很耗时),就可以用到useCallback+memo的组合了,(memo是一个包裹组件的的api,在组件接收的prop不变时,父组件重新渲染不会触发该组件的重新渲染) 场景二: 当方法需要在useEffect中调用时 因为需要添加到dependency list中,如果每次都重新初始化的话会导致useEffect每次都重新调用,因此可以将方法用useCallback包裹起来 场景三: 优化自定义hooks React建议在自定义的钩子中,所有返回的函数都用useCallback包裹 这能确保在使用这个自定义hook的时候,可以做一些性能上的优化,参照场景一二 useDeferredValue vs Suspense useDeferredValue是一个推迟渲染的钩子,他是使React先用useDeferredValue传入的更新之前的值先渲染一次,如果值有更新的话再用更新后的值再渲染一次。通常和useState共同使用 首次渲染时,deferred value就是传入的默认值更新渲染时,react会先不更新deferred value用旧的值先渲染一次,再用更新后的值再渲染一次。注意这个再次更新是可打断的,如果再次更新deferred value,react会打断这个渲染,而使用新的数据重新渲染,用户看到的始终都是原始的数据渲染的结果 另外再使用useDeferredValue,做性能优化时,需要和memo配合使用。 memo的作用是传入的prop没变化则不更新组件。

  • React Hooks 之 useReducer

    useReducer 另外一个可以管理state的hook,常被用来处理复杂的state管理。在实际开发中,基本上普通类型的state,如number, boolean, string等直接使用useState就可以了,涉及到复杂的state一般用useReducer来管理。 useReducer接收两个参数,一个reducer函数,一个初始state,他会返回更新后的state和更新state的方法dispatch 注意事项:

  • React18 新特性

    React18 新特性

    React18提供了全新的创建根节点的方式: ReactDOM.createRoot(),用这个创建根节点可以开启React18的全新特性。如Concurrent模式等。 那么React18到底带来了哪些新的功能呢?我们一起来看一下。 Concurrent Rendering(并行渲染) 时间切片:在并行渲染的模式下,当更新任务无法在一帧之内完成时,任务会被切割为多个任务进行可中断的更新,以此来保证没帧都有多余的时间来进行渲染 更新优先级: 更新的任务会有优先级,更高优先级的任务会打断当前执行的任务先进行处理,处理之后再恢复执行原来的任务。 startTransition & useTransition 设想一个长列表搜索的场景,当用户输入搜索时,需要按照条件渲染出长列表,这就会造成卡顿。 实际上这里有两次渲染,即输入框的渲染和列表的渲染。常规的解决办法是利用debounce防抖。 React现在提供了一个内置的方法,可以更好的处理这种问题 用startTransition包裹执行的任务会被标记为不紧急渲染,因此会被其他紧急的任务抢占先机 useTransition和startTransition的功能一样,使用方法 setState合并更新 在react18之前,setState是合并更新,在setTimeOut中是按同步顺序更新的 在react18之后,所有的setState都是合并更新的了 setState的队列 正常情况下我们在setState中传入的是state的值,但当我们传入的是方法的时候React就会按照队列的方式执行 这里的原则就是传入的是方法就会被添加到队列中去执行,而传入的是值就会替换原来的值 suspense suspense用于在组件渲染之前的等待期间显示Suspense的callback内容, suspense内的组件优先级会比其他组件的优先级更低

  • 深入理解React – 6 – 并发渲染

    React的 Concurrent Renderer 并发渲染器 React18之后引入了并发渲染器,concurrent renderer,根节点使用createRoot, 他主要有以下两个特点 时间切片 因为浏览器16.6ms刷新一次,如果单个任务执行时间超过16.6ms就会造成卡顿,因此并发模式引入了一个shouldYield的判断,会每隔5ms打断一次,这就是时间切片。打断之后会重新恢复调度渲染,通过这种打断和恢复实现了并发。 另外scheduler可以通过优先级来对任务进行排序,这样就实现了高优先级先处理。 每次的setState渲染都是有scheduler维护的,它有一个任务队列,上个任务执行完毕再执行下一个,被打断的任务会被添加到任务队列里。 优先级调度 Schedular处理事件的优先级是按照如下排列的 ImmediatePriority是指离散型的事件,如click, keydown, input等UserBlockingPriority指连续性的事件,如scroll, drag, mouseOver等 而React处理的优先级采用的是Lane。 React是使用二进制来存储这个lane优先级的

  • 深入理解React – 5 – Fiber

    React Fiber架构 React在16.3之后引入了Fiber架构。 我们知道生命周期主要有三个阶段,即渲染阶段,调和阶段,提交阶段。 在引入fiber结构之前,React的diff算法是采用深度优先遍历(DFS)的算法,是一个递归的过程,当DOM结构复杂时调和需要的时间过长,会导致js主线程被长时间占用,因此会出现卡顿,交互无反应等问题。 为了解决这个问题才引入了fiber架构。 就是在调和阶段将原来的栈调和(stack reconcilation) 改为新的fiber调和。 调和和diff算法有什么关联呢? 调和就是指将实际DOM按照虚拟DOM渲染成一致的过程, 而diff算法只是这个过程中的一个步骤,也就是说调和是包含diff算法的。 fiber架构的diff算法主要从以下几点进行了突破 React Fiber架构有哪些优点呢? 三大优点,可中断,可恢复,优先级 fiber中增加了一个scheduler 详细说下可中断和可恢复,在插入一个优先级更高的任务时,传统树需要把后边所有未处理的任务保存起来,这会带来很大的开销,而fiber树只需要把当前节点记录在一个变量里就可以了,下次更新可以按照逻辑先遍历自己,再child, 再siblings 优先级 child -> siblings -> return 这里有两个处理方式,从beginWork开始,向下面的children寻找是调用beginWork,当没有子节点时,调用completeUnitOfWork回到兄弟节点 Fiber树 Fiber树和传统树有很大的不同,父节点只和他的第一个子节点相连接,而第一个子节点和后边的兄弟节点相连接,每个孩子都有一个指向父亲节点的指针,这样构成了一个单向链表的结构 Fiber树有新旧两个树,即current tree和workInProgress tree current tree 存储当前页面中每个组件状态 更新节点时,根据current tree和一些修改过的参数形成 workInProgress tree。 最终React将workInProgress tree渲染到页面,再把他复制成current tree。 在构建current树时,是边构建边diffing的,如果构建到一半被打断,那么current树还在,如果构建完成了则删除掉current树,workInProgress树成为新的current树 所以说旧的diff算法是对比虚拟DOM和真实DOM的区别,fiber的diff算法是对比两棵树的区别

  • 深入理解React – 4 – Hooks

    React Hooks 我们知道从React16以来React官方团队引入了新的组件书写方式,即Function Components,从Class Components 过渡到Function Components, 从两者兼容,到鼓励大家使用Function Components这中间发生了什么呢? 我们一起来看一下吧。 Question: 为什么要从Class Components过渡到Function Components呢? 第一点就是组件的复用更容易了,React在引入Function Component的同时也引入了React Hooks这一概念,我们可以通过编写自定义Hook 来代替原来的HOC或者Render Props。 自定义hooks更加易于理解,也对我们研发人员更友好 第二点class components的this指向问题,在class组件中,一些改变DOM的操作,虽然props是不变的,但是this的指向是有可能发生改变的,如果this上的数据被修改可能造成意想不到的变化,例如用setTimeout延迟渲染,等待的过程中就有可能打破this.props和渲染之间的关联。 而在函数组件中每一次调用都是传入参数做新的调用,不会影响上一次操作对DOM的捕获 那么我们再详细聊一下React Hooks吧 常用的React Hooks包括useEffect, useState, useRef, useCallback, useMemo等 useEffect 因为在函数组件中我们不能再使用生命周期的方法了,因此React团队引入了useEffect用来处理组件的副作用(side effects)。 说到副作用,一般认为脱离了React作用范围的功能都叫做side effects, 例如webStorage的操作, fetch data等。 常用的生命周期componentDidMount, componentDidUpdate, componentWillUnmount都可以用useEffect代替,但官方并不鼓励我们用useEffect时去思考代替原来的生命周期,因为useEffect是一种完全不同的设计模式,他的出现就是为了处理副作用。 useState 用来在函数组件中设置和更新变量 useRef useRef用来操作DOM,需要先做好绑定操作,常用来制作动画效果 useCallback (性能优化) React在重新渲染组件时需要重新渲染组件和方法,因此在复杂应用需要同时处理上百个组件的情况就会造成很大的开销。因为方法造成的开销更大,因此React引入了useCallback这一个hook,useCallback会缓存方法,注意并不是缓存方法执行的结果,他会在参数没有变化时返回原有的方法。他和useEffect一样,需要指定一个以来的数组,在数组没有变化时不再重新初始化这个方法。dependency对比是用Object.is做对比的(==会转换对比的类型;===对比+0和-0会返回true,Object.is返回false; ===对比两个NaN会返回false, Object.is会返回true) useMemo(性能优化) 同样是性能优化的常用手段,useMemo是存储计算的结果,只有在依赖数组发生变化时才会重新计算 React Hooks的使用原则…

  • 深入理解React – 3

    用过Vue和React的小伙伴都知道,两个框架一个很重要的区别就是数据的传递。在Vue中数据是双向绑定的,而在React中的数据是严格单向传递的,今天我们来总结一下React中数据传递的方式 单向数据流 当前组件的state以props的形式流动时,只能流向比自己层级更低的组件: 以上的单向数据流是React数据中的基础传递方式,在实际项目中,组件嵌套的层级都会很深,我们就需要用到更加方便的信息传递方式了 状态集中管理 最经典的就是Redux啦,作为集中式状态管理容器,他提供可预测的状态管理,设计模式遵循flux模式,他的数据流也是严格单向的。 另外一个就是React内置的contextAPI啦,contextAPI使用起来更方便,在中小型的项目中也就没必要使用Redux啦。 ContextAPI一般需要createContext, useContext, context.Provider三大件,createContext创建一个全局环境,用provider包裹需要用到的组件,useContext在组件中调用。 发布订阅模型 接下来介绍状态管理的大杀器Zustand,最近的项目中一直在使用这个工具,比context API 和 Redux方便了无数倍 首先为什么选择Zustand呢? 按照官方的话来说Zustand是一个轻量,无样板,基于hooks的状态管理库。他的核心优势是: 对于常见的三种state,我们是有三种心智模型的 核心区别Zustand管理的是本地真相,React Query管理的是远程真相 实践 1. 按业务域区分slice 2. 组合Store – useBoundStore 3. 持久化 persist + partialize 4. 订阅 4.1 基础版订阅 4.2 进阶版订阅 selector + shallow 4.3 终极版订阅 *** 亮点 更加推荐的架构是不让组件直接接触store,而是通过语义化store做一个中间层 踩过的坑: 1. 直接解构整个store 等价于订阅整个store,任意的状态变化都会导致这个组件重新渲染 2. 把服务端数据塞进 Zustand 带来的问题是需要自己处理缓存,失效,刷新等机制,并且状态很快就失真了,因此还是建议直接使用react query了…

  • 深入理解React – 2

    深入理解React – 2

    揭开React虚拟DOM的神秘面纱

  • 深入理解React – 1

    深入理解React – 1

    深入学习React之 JSX工作流