react
- Virtual Dom模型
- 生命周期管理
- setState机制
- diff算法
- React patch、事件系统
- react是一个view层的展现库,要想实现对页面数据和路由的管理还需要配合其它的库。这其中最常用的就是redux和react-router库。通过redux库能够统一管理页面数据,保证数据的单向流动,其大概流程是 用户触发页面交互,页面根据用户交互产生一个action并将这个action通过store的dispatch方法传给sotre,store根据一定关系找到reducer,reducer根据action的type类型产生新的state值并将新产生的state值回传给store,store根据最新的state通知view重新渲染页面(通过调用render函数)。而react-rouer则用来控制react中路由跳转。这样通过react redux react-router相互配合形成前端页面结构。
生命周期
首先是给三个生命周期函数加上了 UNSAFE:
- UNSAFE_componentWillMount
- UNSAFE_componentWillReceiveProps
- UNSAFE_componentWillUpdate 这里并不是表示不安全的意思,它只是不建议继续使用,并表示使用这些生命周期的代码可能在未来的 React 版本(目前 React17 还没有完全废除)存在缺陷,如 React Fiber 异步渲染的出现。 同时新增了两个生命周期函数:
- getDerivedStateFromProps getDerivedStateFromProps 会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。使用 props 来派生/更新 state。静态方法不依赖组件实例而存在,故在该方法内部是无法访问 this 的
- getSnapshotBeforeUpdate 在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期的任何返回值将作为第三个参数传入componentDidUpdate(prevProps, prevState, snapshot),getSnapshotBeforeUpdate 会在最终的 render 之前被调用,也就是说在 getSnapshotBeforeUpdate 中读取到的 DOM 元素状态是可以保证与 componentDidUpdate 中一致的。getSnapshotBeforeUpdate 返回的数据在 componentDidUpdate 中用完即被销毁,效率更高。
挂载阶段: constructor(props): 实例化。 static getDeriverdStateFromProps 从 props 中获取 state。 render 渲染。 componentDidMount: 完成挂载。
更新阶段: static getDeriverdStateFromProps 从 props 中获取 state。 shouldComponentUpdate 判断是否需要重绘。 render 渲染。 getSnapshotBeforeUpdate 获取快照。 componentDidUpdate 渲染完成后回调。
卸载阶段: componentWillUnmount 即将卸载。
错误处理: static getDerivedStateFromError 从错误中获取 state。 componentDidCatch 捕获错误并进行处理。
虚拟DOM
而React会先将你的代码转换成一个JavaScript对象,然后这个JavaScript对象再转换成真实DOM。这个JavaScript对象就是所谓的虚拟DOM。当我们需要创建或更新元素时,React首先会让这个VitrualDom对象进行创建和更改,然后再将VitrualDom对象渲染成真实DOM;
为什么要使用虚拟DOM
- MVVM框架解决视图和状态同步问题
- 模板引擎可以简化视图操作,没办法跟踪状态
- 虚拟DOM跟踪状态变化
- 参考github上virtual-dom (github.com/Matt-Esch/v…) 的动机描述
-
- 虚拟DOM可以维护程序的状态,跟踪上一次的状态
- 通过比较前后两次状态差异更新真实DOM
- 跨平台使用
-
- 浏览器平台渲染DOM
- 服务端渲染SSR(Nuxt.js/Next.js),前端是vue向,后者是react向
- 原生应用(Weex/React Native)
- 小程序(mpvue/uni-app)等
- 真实DOM的属性很多,创建DOM节点开销很大
- 虚拟DOM只是普通JavaScript对象,描述属性并不需要很多,创建开销很小
- 复杂视图情况下提升渲染性能(操作dom性能消耗大,减少操作dom的范围可以提升性能)
灵魂发问:使用了虚拟DOM就一定会比直接渲染真实DOM快吗?答案当然是否定的,复杂视图情况下提升渲染性能,因为虚拟DOM+Diff算法可以精准找到DOM树变更的地方,减少DOM的操作(重排重绘)。
虚拟DOM的优势在于优化复杂应用中频繁的DOM操作,但在某些简单场景下,直接操作真实DOM可能会更快。虚拟DOM的初衷是提高开发效率和优化性能,而不是保证在所有情况下都比原生DOM快.
React中的事件处理为什么要bind this
如说我写了一个类组件,有个onClick属性 ,onClick={ this.fun },如果不bind肯定是不行的,下面讲一下为什么要bind this:首先我们知道React是通过创建虚拟DOM 然后将虚拟DOM生成真实的DOM 最后插入到页面中,而React生命周期中render方法的作用就是将虚拟DOM渲染成真实DOM,这里提到了render的实现 render将"on+大写字母"开头的事件属性 转化为"on+小写字母"开头的属性 ,并生成真实DOM,生成真实DOM的同时把这个函数赋值过去。(实际React中使用合成事件,将事件委托到了document对象中),在JSX语法中: onClick={ function } onClick这个属性本身只是一个"中间变量"。将函数赋值给onClick这个中间变量,后面不仅要进行JSX语法转化,将JSX组件转换成Javascript对象,还要再将Javascript对象转换成真实DOM。把onClick作为中间变量,指向一个函数的时候,后面的一系列处理中,使用onClick这个中间变量所指向的函数,里面的this自然就丢失掉了,不是再指向对象实例了。
- JS中的this是由函数调用者调用的时候决定的。当把一个函数作为callback传递给另一个函数的时候,这个函数的this一定是会丢失的, -为什么React没有自动的把bind集成到render方法中呢? 答:因为render多次调用每次都要bind会影响性能,所以官方建议你自己在constructor中手动bind达到性能优化。
React的渲染原理的理解
render的返回结果实际上是React.createElement的执行结果,即一个包含props属性的对象。 JSX是 React.createElement 方法的语法糖,createElement()方法会先通过遍历config获取所有的参数,然后获取其子节点以及默认的props的值。然后将值传递给ReactElement()调用并返回 JS 对象。(每个 react 组件都会使用$$typeof来标识,它的值使用了Symbol数据结构来确保唯一性。)得到了 VDOM,react通过协调算法(reconciliation)去比较更新前后的VDOM,从而找到需要更新的最小操作,减少了浏览器多次操作DOM的成本,通过 VDOM 以及 diff 算法能够只更新必要的 DOM,使用shouldComponentUpdate来判断该组件是否需要 diff,能够节省大量的 diff 运算时间。
全面解析 React 源码 - render 篇
setState是同步的还是异步的
有时表现出异步,有时表现出同步
- 在合成事件和钩子函数当中是异步的,在原生事件和setTimeout当中是同步的
- 异步并不是说内部是由异步代码组成,本身的执行过程和代码都是同步的,只是合成事件和钩子函数的调用在更新之前,导致拿不到数据形成所谓的异步,可以通过setState的第二个参数(是个回调函数,拿到更新数据)
- 批量优化也是建立在异步上面,在原生事件和定时事件中不会批量更新,
- 合成事件(就是我们给那些元素绑定点击事件等等都属于合成事件) 无论调用多少次 setState,都会不会立即执行更新,而是将要更新的·存入_pendingStateQueue,将要更新的组件存入 dirtyComponent,以生命周期为例,所有组件,即最顶层组件 didmount后会将批处理标志设置为 false。这时将取出 dirtyComponent中的组件以及 _pendingStateQueue中的 state进行更新。这样就可以确保组件不会被重新渲染多次。当我们在执行 setState后立即去获取 state,这时是获取不到更新后的 state的,因为处于 React的批处理机制中, state被暂存起来,待批处理机制完成之后,统一进行更新。setState本身并不是异步的,而是 React的批处理机制给人一种异步的假象。
useEffect
的执行时机和方式并非总是异步的,它取决于几个因素:
- 依赖项数组:
useEffect的第二个参数是一个依赖项数组。如果依赖项数组为空 ([]),useEffect只会在组件挂载后执行一次,并且通常是同步的。 这与组件的渲染过程同步进行。 - 依赖项变化: 如果依赖项数组不为空,
useEffect会在每次依赖项发生变化时执行。 在这种情况下,useEffect的执行时机通常是异步的,因为它会在浏览器完成当前渲染周期后执行。 这是为了避免不必要的重新渲染和性能问题。 - 浏览器渲染机制: 浏览器的渲染机制本身是异步的。即使
useEffect本身是同步执行的,它也可能受到浏览器渲染机制的影响,导致其执行结果在下一个渲染周期中才能反映出来。 - 异步操作在useEffect内部: 如果
useEffect内部包含异步操作(例如setTimeout、fetch、Promise),那么即使依赖项数组为空,useEffect的执行也会包含异步部分。 异步操作的完成和结果的反映仍然是异步的。
import { useRef, useState } from 'react';
function useSimplifiedEffect(effect, dependencies) {
const effectRef = useRef(effect);
const dependenciesRef = useRef(dependencies);
const [hasRun, setHasRun] = useState(false);
// 依赖项比较
const areDependenciesEqual = (prevDeps, nextDeps) => {
if (prevDeps === nextDeps) return true;
if (!prevDeps || !nextDeps || prevDeps.length !== nextDeps.length) return false;
for (let i = 0; i < prevDeps.length; i++) {
if (prevDeps[i] !== nextDeps[i]) return false;
}
return true;
};
// useEffect 模拟
if (!hasRun || !areDependenciesEqual(dependenciesRef.current, dependencies)) {
effectRef.current();
dependenciesRef.current = dependencies;
setHasRun(true);
}
}
export default useSimplifiedEffect;
useReducer
useReducer的核心原理是利用 useState 来存储状态,并通过一个 reducer 函数来更新状态。 它本质上是对状态更新逻辑的封装,使得状态更新更加结构化和可预测
import { useState } from 'react';
function useMyReducer(reducer, initialState) {
const [state, setState] = useState(initialState);
const dispatch = (action) => {
if (typeof action === 'function') {
// 异步action,是一个函数
action(dispatch); // 将dispatch传递给异步action函数
} else {
// 同步action
const newState = reducer(state, action);
setState(newState);
}
};
return [state, dispatch];
}
export default useMyReducer;
高阶组件
高阶组件是参数为组件,返回值为新组件的函数。
- 属性代理 将被处理组件的props和新的props一起传递给新组件
- 反向继承 继承组件,拿到生命周期等钩子函数,可以做渲染劫持
- 缺点
- 扩展性限制: HOC 无法从外部访问子组件的 State因此无法通过shouldComponentUpdate滤掉不必要的更新,React 在支持 ES6 Class 之后提供了React.PureComponent来解决这个问题
- Ref 传递问题: Ref 被隔断,后来的React.forwardRef 来解决这个问题
- Wrapper Hell: HOC可能出现多层包裹组件的情况,多层抽象同样增加了复杂度和理解成本
- 命名冲突: 如果高阶组件多次嵌套,没有使用命名空间的话会产生冲突,然后覆盖老属性
- 不可见性: HOC相当于在原有组件外层再包装一个组件,你压根不知道外层的包装是啥,对于你是黑盒
渲染劫持
HOC(高阶组件)有一种反向继承的用法,高阶组件可以在render函数中做非常多的操作,从而控制原组件的渲染输出,只要改变了原组件的渲染,我们都将它称之为一种渲染劫持。
mixin
- 1.mixin的作用是抽离公共功能,不存在渲染dom的需要,所以它没有render方法。如果你定义了render方法,那么他会和组件的render方法冲突而报错。
- 2.mixin不应该污染state,所以他也没有 setState 方法。
- 3.mixin应该只提供接口(即方法),不应该提供任何属性。 Mixin的缺陷:
- 组件与 Mixin 之间存在隐式依赖(Mixin 经常依赖组件的特定方法,但在定义组件时并不知道这种依赖关系)
- 多个 Mixin 之间可能产生冲突(比如定义了相同的state字段)
- Mixin 倾向于增加更多状态,这降低了应用的可预测性(The more state in your application, the harder it is to reason about it.),导致复杂度剧增
- 隐式依赖导致依赖关系不透明,维护成本和理解成本迅速攀升:
Context
- Context 通过组件树提供了一个传递数据的方法,从而避免了在每一个层级手动的传递 props 属性。
- Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。 Context 主要应用场景在于很多不同层级的组件需要访问同样一些的数据
React.lazy方法可以异步加载组件文件。
- React.lazy的用法
React.lazy方法可以异步加载组件文件。
const Foo = React.lazy(() => import('../componets/Foo)); React.lazy不能单独使用,需要配合React.suspense,suspence是用来包裹异步组件,添加loading效果等。
<React.Suspense fallback={<div>loading...</div>}>
<Foo/>
</React.Suspense>
- React.lazy原理
React.lazy使用import来懒加载组件,import在webpack中最终会调用requireEnsure方法,动态插入script来请求js文件,类似jsonp的形式。
React如何实现自己的事件机制
- React将事件规范化,以便它们在不同的浏览器中具有一致的属性。React事件并没有绑定在真实的 Dom节点上,而是通过事件代理,在最外层的 document上对事件进行统一分发。
- 因为合成事件的触发是基于浏览器的事件机制来实现的,通过冒泡机制冒泡到最顶层元素,然后再由 dispatchEvent 统一去处理,
- 原生事件先于合成事件执行
- React 通过对象池的形式管理合成事件对象的创建和销毁,减少了垃圾的生成和新对象内存的分配,提高了性能
- (原生事件:子元素 DOM 事件监听!
- 原生事件:父元素 DOM 事件监听!
- React 事件:子元素事件监听!
- React 事件:父元素事件监听!
- 原生事件:document DOM 事件监听! )
什么时候使用状态管理器
- 某个组件的状态,需要共享
- 某个状态需要在任何地方都可以拿到
- 一个组件需要改变全局状态
- 一个组件需要改变另一个组件的状态
render函数中return如果没有使用()会有什么问题?
babel 会将 JSX 语法编译成 js,同时会在每行自动添加分号(;),如果 return 后换行了,那么就会变成 return;渲染没有返回任何内容。这通常意味着缺少 return 语句。 不换行没问题,换行了就得用()包住。
componentWillUpdate可以直接修改state的值吗?
react 组件在每次需要重新渲染时候都会调用 componentWillUpdate(), 例如,我们调用 this.setState()时候 在这个函数中我们之所以不调用 this.setState()是因为该方法会触发另一个 componentWillUpdate(),如果我们 componentWillUpdate()中触发状态更改,我们将以无限循环.
React 的插槽(Portals)
react 中的插槽(Portals),通过 ReactDOM.createPortal(child, container)创建,是 ReactDOM 提供的接口,可以实现将子节点渲染到父组件 DOM 层次结构之外的 DOM 节点
React组件的构造函数
- 指定this --> super(props)
- 设置初始化的状态 --> this.setState({});
- 为组件上的构造函数绑定this 不写构造函数,默认会被隐式调用
函数式组件与类组件有何不同?
- 在hooks出现之前,react中的函数组件通常只考虑负责UI的渲染,没有自身的状态没有业务逻辑代码,是一个纯函数。下面这个函数组件就是一个纯函数,它的输出只由参数props决定,不受其他任何因素影响。 但是这种函数组件一旦我们需要给组件加状态,那就只能将组件重写为类组件,因为函数组件没有实例,没有生命周期。所以我们说在hook之前的函数组件和类组件最大的区别又是状态的有无。
- hooks为函数组件提供了状态,也支持在函数组件中进行数据获取、订阅事件解绑事件等等
- 状态同步问题,函数组件会捕获当前渲染时所用的值。首先我们知道,不论是函数式组件还是类组件,只要状态或者props发生变化了那就会重新渲染,而且对于没有进行过性能优化的子组件来说,只要父组件重新渲染了,子组件就会重新渲染。而且在react中props是不可变的,而this是一直在改变的。所以类组件中的方法可以获取到最新的实例即this,而函数组件在渲染的时候因为闭包的原因捕获了渲染时的值,让类组件获取渲染时的值,类组件利用闭包,对于类组件我们将函数定义在render函数当中,这样我们就形成了一个闭包,就可以像函数组件一样在渲染的时候捕获相应的值;
- 函数组件useEffect与类组件生命周期,
- 性能优化: 类组件shouldComponentUpdate这个生命周期,通常我们在这个生命周期中进行组件的优化,通过判断前一个props和当前的props是否有变化来判断组件是否需要渲染,或者通过PureComponent实现;那么在函数组件中我们通过React.memo()来实现, 但是当父组件将自己定义的引用类型的值传递给子组件时,即使值没有改变。但是由于每次渲染的时候都会生成新的变量,导致引用发生了改变,所以子组件仍然会渲染,考虑使用useCallback,useMemo来实现优化
- 代码复用: 类组件用高阶组件,函数组件用自自定义hook
hook的优缺点有以下一些总结: 优点: 1. 通过自定义hook更加易于复用代码 2. 函数式编程代码更加清晰 3. 更方便拆分组件 4. 不用考虑类组件中的this 缺点: 1. 响应式的依赖,当业务逻辑复杂时,依赖更加难以管理和维护 2. 状态不同步,异步逻辑中可能会出现状态不是最新的(具体需要看需求)
总结如下: 1、在异步更新时:
类组件有 this,可以通过 this.state.value 指向最新的值,不想指向最新的值可以使用闭包的办法。 函数组件没有this,捕获了渲染时(例如点击时)所使用的值,可以通过 useRef 来拿到最新的值
2、在使用 hooks 时:
在处理类似于 intervals 和 subscriptions 这样的命令式API时,ref 会十分便利。可以用 ref 跟踪任何值 —— 一个prop,一个state变量,整个props对象,或者甚至一个函数。然后可以使用 useCallback 来优化,reducer 可能是更好的选择。
要记得使用 useEffect、useCallback。还要了解 useReduce 和 useCallback 之间的区别,以更好的选择他们。
使用了很多函数组件之后,要记得优化代码和了解什么值会随着时间而改变
3、hooks 的心法: “写代码时要认为任何值都可以随时更改”。
react项目中异常捕获处理
- componentDidCatch,Error boundaries
- window.onerror全局捕获异常,window.addEventListener('error')
- 使用try catch捕获
react diff的原理
- Web UI中DOM节点跨层级的移动操作特别少,可以忽略不计。
- 拥有相同名字的两个组件将会生成相似的树形结构,拥有不同名字的两个组件将会生成不同的树形结构。
- 对于同一层级的一组子节点,它们可以通过唯一 id 进行区分。 在上面三个策略的基础上,React 分别将对应的tree diff、component diff 以及 element diff 进行算法优化,极大地提升了diff效率。
- tree diff: 1.只会对相同层级的节点进行比较; 2.只有删除、创建操作,没有移动操作; 3.由于没做性能优化,所以官方建议少做这样的跨层级操作
- component diff:.如果是同一个名字的组件,则会继续往下diff运算; 2.如果不是一个名字的组件,那么直接删除旧的,创建新的;对于同一个名字的组件,用户可以控制其不要进行diff运算,具体就是,用户可以使用shouldComponentUpdate()来告诉react要不要对此组件进行diff运算
- element diff的时候,总是比较同一层级的节点们,这是前提,接下来提供了三种节点的操作:插入:INSERT_MARKUP删除:REMOVE_NODE移动:MOVE_EXISTING,注意:每个节点都有在他们的层级唯一的key作为标识
react怎么提高列表渲染的性能?
react-virtualized原理:核心原理:只渲染你所见的。上面的应用渲染了1000条评论,但屏幕只为你展示了10来条数据,那另外990条的渲染就是浪费的。如果我们只渲染可见的评论,当鼠标滚动查看更多的时候,将新的节点替换旧的节点。
react 性能优化
- Code Splitting
- shouldComponentUpdate避免重复渲染
- useCallback :Memoized Function
- useMemo :Memozied Value
- 不需要渲染的props,合理使用context机制,或公共模块(比如一个单例服务)变量来替换。
- 使用不可突变数据结构
- 组件尽可能的进行拆分、解耦
- 列表类组件优化
- bind函数优化
- 不要滥用props
- ReactDOMServer进行服务端渲染组件
- 不使用跨层级移动节点的操作。
- 对于条件渲染多个节点时,尽量采用隐藏等方式切换节点,而不是替换节点。
- 尽量避免将后面的子节点移动到前面的操作,当节点数量较多时,会产生一定的性能问题。
组件在什么时候render
在React中,每当触发更新(比如调用this.setState、useState),会为组件创建对应的fiber节点。 父组件render时
浅层渲染
当为 React 写单元测试时,浅层渲染(Shallow Renderer) 会变得十分有用。浅层渲染使你可以渲染 “单层深度” 的组件,并且对组件的 render 方法的返回值进行断言,不用担心子组件的行为,组件并没有实例化或被渲染。浅渲染并不需要 DOM。
useState和this.state区别
useState内部基于 useReducer 实现,方法返回 state 本身以及一个修改 state 的方法。 通过 setXXX 修改数据,不会和 setState 一样进行对象属性合并,会直接覆盖。 Hooks 函数组件中,存在渲染闭包的概念,在一次渲染闭包中,state 是固定不变的。 Hooks 函数组件,默认开启 类 Object.is 的浅层比较,类似默认开启 PureComponent 的优化方式。
Fiber架构的理解
React 15 的 StackReconciler 方案由于递归不可中断问题,如果 Diff 时间过长(JS计算时间),会造成页面 UI 的无响应的表现,vdom 无法应用到 dom 中。 为了解决这个问题,React 16 实现了新的基于 requestIdleCallback 的调度器(因为 requestIdleCallback 兼容性和稳定性问题,自己实现了 polyfill),通过任务优先级的思想,在高优先级任务进入的时候,中断 reconciler。为了适配这种新的调度器,推出了 FiberReconciler,将原来的树形结构(vdom)转换成 Fiber 链表的形式(child/sibling/return),整个 Fiber 的遍历是基于循环而非递归,可以随时中断。
能够将可中断的任务拆分成块。 能够对进程中的工作划分优先级、重新设定基址(Rebase)、恢复。 能够在父子之间来回反复,借此为 React 的 Layout 提供支持。 能够通过 render() 返回多个元素。 为错误边界提供了更好的支持。
Fiber 的主要工作流程:
- ReactDOM.render() 引导 React 启动或调用 setState() 的时候开始创建或更新 Fiber 树。
- 从根节点开始遍历 Fiber Node Tree, 并且构建 WokeInProgress Tree(reconciliation 阶段)。
-
- 本阶段可以暂停、终止、和重启,会导致 react 相关生命周期重复执行。
- React 会生成两棵树,一棵是代表当前状态的 current tree,一棵是待更新的 workInProgress tree。
- 遍历 current tree,重用或更新 Fiber Node 到 workInProgress tree,workInProgress tree 完成后会替换 current tree。
- 每更新一个节点,同时生成该节点对应的 Effect List。
- 为每个节点创建更新任务。
- 将创建的更新任务加入任务队列,等待调度。
-
- 调度由 scheduler 模块完成,其核心职责是执行回调。
- scheduler 模块实现了跨平台兼容的 requestIdleCallback。
- 每处理完一个 Fiber Node 的更新,可以中断、挂起,或恢复。
- 根据 Effect List 更新 DOM (commit 阶段)。
-
-
React 会遍历 Effect List 将所有变更一次性更新到 DOM 上。
-
这一阶段的工作会导致用户可见的变化。因此该过程不可中断,必须一直执行直到更新完成。
-
react为什么需要fiber架构,vue为什么不需要fiber架构**
react知道哪个组件触发了更新,但是不知道哪些子组件会受到影响。因此react需要生成改组件下的所有虚拟DOM结构,与原本的虚拟DOM结构进行对比,找出变动的部分。
在vue中,一切影响页面内容的数据都应该是响应式的,vue通过拦截响应式数据的修改,知道哪些组件应该被修改。不需要遍历所有子树。vue的diff算法是对组件内部的diff,如果存在子组件,会判断子组件上与渲染相关的属性是否发生变化,无需变化的化则复用原本的DOM,不会处理子组件。 模板语法让vue能够进行更好地编译时分析,提高优化过程的效率,react缺少这部分,无法识别哪些是静态节点,哪些是动态节点。
Time Slice的理解
时间分片
- React 在渲染(render)的时候,不会阻塞现在的线程
- 如果你的设备足够快,你会感觉渲染是同步的
- 如果你设备非常慢,你会感觉还算是灵敏的
- 虽然是异步渲染,但是你将会看到完整的渲染,而不是一个组件一行行的渲染出来
- 同样书写组件的方式
window.requestIdleCallback
window.requestIdleCallback()方法将在浏览器的空闲时段内调用的函数排队。这使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应。函数一般会按先进先调用的顺序执行,然而,如果回调函数指定了执行超时时间timeout,则有可能为了在超时前执行函数而打乱执行顺序。
你可以在空闲回调函数中调用requestIdleCallback(),以便在下一次通过事件循环之前调度另一个回调。
setState的第一个参数是callback而不是一个对象
React 可能会把多个 setState() 调用合并成一个调用。 异步更新的,你不能依赖他们的值计算下一个state(状态)。为了弥补这个问题,使用另一种 setState() 的形式,接受一个函数。这个函数将接收前一个状态作为第一个参数,应用更新时的 props 作为第二个参数,
react 中间件
中间件很好理解,就是一个处理过程,有输入有输出,但是中间加入了一些其它操作,类似于设计模式中的装饰模式。经过中间件的处理后,能够增加一些其它功能,比如日志记录功能,数据上报功能等等。 react中中间件也是以高阶组件的形式出现,
- 1、中间件:就是为了解决异步数据流的问题
- 2、applyMiddleware:是一个存在于action和reducer之间的一个操作,就是为了阻止数据直接进行修改
- 3、自定义拦截action-->reducer的过程,变为action-->applyMiddleware-->reducer 这种机制可以让我们改变数据流 实现如异步action action过滤 日志输出 异常报告等功能
- 4、Reducer:是纯函数 不能处理过多的业务逻辑,只能处理state的变化
- 5、以前的同步redux写法:action-->reducer(state,action)-->store(reducer) 有中间件以后的写法:action-->applyMiddleware()-->reducer(state,action)-->store(reducer)
- 6、store.dispatch():是store提供的派生方法,可以异步的去操作或者派发某个行为变化使用
- 7、logger:它不是Redux的API,而是一个单独的第三方包(redux-logger)的中间件的辅助处理,可以返回最新的state数据,和applyMiddleware中间件配合使用
react 中的循环(遍历)
- 遍历数组,我们通常使用map方法
- 遍历对象,两种方法(for...in循环做成自运行函数、Object.entries(obj)将对象转换成数组再使用map方法)
react的状态提升
React的状态提升就是用户对子组件操作,子组件不改变自己的状态,通过自己的props把这个操作改变的数据传递给父组件,改变父组件的状态,从而改变受父组件控制的所有子组件的状态,这也是React单项数据流的特性决定的。 官方的原话是:共享 state(状态) 是通过将其移动到需要它的组件的最接近的共同祖先组件来实现的。 这被称为“状态提升(Lifting State Up)”。
升级 React 版本 记录/文档
- 为了渲染优化,减少体积,SSR服务,新的声明周期,新的react context,react mome,react lazy
- 升级react包,生命周期的替换,1,有很多组件在 componentWillReceiveProps 异步延时调用 setState …… ,而 getDerivedStateFromProps 中无法 异步更新 state ,所以做了大量工作去优化重写,实在优化不了的在 DidUpdate 去调用 setState(会造成多次渲染,但组件树不复杂的情况,忽略这些开销)2,之前很多逻辑只依赖于 componentWillReceiveProps ,现在用 getDerivedStateFromProps 同 componentDidUpdate 去替换,导致逻辑分层,有些相似的逻辑要重复写
react 组件通信
props, props+回调 ,context,redux,eventbus
redux
- Store:保存数据的地方,你可以把它看成一个容器,整个应用只能有一个Store。
- State:Store对象包含所有数据,如果想得到某个时点的数据,就要对Store生成快照,这种时点的数据集合,就叫做State。
- Action:State的变化,会导致View的变化。但是,用户接触不到State,只能接触到View。所以,State的变化必须是View导致的。Action就是View发出的通知,表示State应该要发生变化了。
- Action Creator:View要发送多少种消息,就会有多少种Action。如果都手写,会很麻烦,所以我们定义一个函数来生成Action,这个函数就叫Action Creator。
- Reducer:Store收到Action以后,必须给出一个新的State,这样View才会发生变化。这种State的计算过程就叫做Reducer。Reducer是一个函数,它接受Action和当前State作为参数,返回一个新的State。
- dispatch:是View发出Action的唯一方法。
然后我们过下整个工作流程:
-
1、首先,用户(通过View)发出Action,发出方式就用到了dispatch方法。
-
2、然后,Store自动调用Reducer,并且传入两个参数:当前State和收到的Action,Reducer会返回新的State
-
3、State一旦有变化,Store就会调用监听函数,来更新View。
-
redux 是一个应用数据流框架,主要是解决了组件间状态共享的问题,原理是集中式管理,主要有三个核心方法,action,store,reducer,工作流程是 view 调用 store 的 dispatch 接收 action 传入 store,reducer 进行 state 操作,view 通过 store 提供的 getState 获取最新的数据,flux 也是用来进行数据操作的,有四个组成部分 action,dispatch,view,store,工作流程是 view 发出一个 action,派发器接收 action,让 store 进行数据更新,更新完成以后 store 发出 change,view 接受 change 更新视图。Redux 和 Flux 很像。主要区别在于 Flux 有多个可以改变应用状态的 store,在 Flux 中 dispatcher 被用来传递数据到注册的回调事件,但是在 redux 中只能定义一个可更新状态的 store,redux 把 store 和 Dispatcher 合并,结构更加简单清晰
-
新增 state,对状态的管理更加明确,通过 redux,流程更加规范了,减少手动编码量,提高了编码效率,同时缺点时当数据更新时有时候组件不需要,但是也要重新绘制,有些影响效率。一般情况下,我们在构建多交互,多数据流的复杂项目应用时才会使用它们
react组件的state和props两者有什么区别
- State 是一种数据结构,用于组件挂载时所需数据的默认值。State 可能会随着时间的推移而发生突变,但多数时候是作为用户事件行为的结果。
- Props(properties 的简写)则是组件的配置。props 由父组件传递给子组件,并且就子组件而言,props 是不可变的(immutable)。组件不能改变自身的 props,但是可以把其子组件的 props 放在一起(统一管理)。Props 也不仅仅是数据–回调函数也可以通过 props 传递
react中遍历时为什么不用索引作为唯一的key值?
因为在运行过程中可能调整顺序使索引改变(例如拖拽等),组件实际上仍然是那几个,但 key 却发生了变化,这是不合理的
描述下在react中无状态组件和有状态组件的区别是什么?
- 无状态组件主要用来定义模板,接收来自父组件props传递过来的数据,使用{props.xxx}的表达式把props塞到模板里面。无状态组件应该保持模板的纯粹性,以便于组件复用。
- 有状态组件主要用来定义交互逻辑和业务数据(如果用了Redux,可以把业务数据抽离出去统一管理),使用{this.state.xxx}的表达式把业务数据挂载到容器组件的实例上(有状态组件也可以叫做容器组件,无状态组件也可以叫做展示组件),然后传递props到展示组件,展示组件接收到props,把props塞到模板里面。
reducer 必须是一个纯函数
固定的输入,有固定的输出;且不包含任何副作用的函数称之为纯函数;比如说reducer
,reducer的职责不允许有副作用,副作用简单来说就是不确定性,如果reducer有副作用,那么返回的state就不确定,前端UI拿到的数据也不确定了,所以就是这个环节引入了副作用,redux设计好的规范就被你破坏了,
store 里的 state 是一个引用类型,多个组件都可能共享这个 state,如果允许直接在组件中修改这个 state,由于组件间千丝万缕的关系,复杂度会变得很高,定位问题会变得异常困难,
如何解决这个副作用,这里的请求可以放在reducer之前,你先请求,该做出错处理的就做出错处理,等拿到实际数据后在发送action来调用reducer。这样通过前移副作用的方式,使reducer变得纯洁。
如果 reducer 不是纯函数会发生什么? 我们将上面代码reducer改造一下,直接修改 state,而不是返回新的 state
改变代码后,我们发现当我们触发了 action 以后,页面没有发生任何变化,这是为什么呢? 通过源代码,我们发现,var nextStateForKey = reducer(previousStateForKey, action), nextStateForKey就是通过 reducer 执行后返回的结果(state),然后通过hasChanged = hasChanged || nextStateForKey !== previousStateForKey来比较新旧两个对象是否一致,此比较法,比较的是两个对象的存储位置,也就是浅比较法,所以,当我们 reducer 直接返回旧的 state 对象时,Redux 认为没有任何改变,从而导致页面没有更新。
为什么 Redux 会这样设计?
因为比较两个 javascript 对象中所有的属性是否完全相同,唯一的办法就是深比较,然而,深比较在真实的应用中代码是非常大的,非常耗性能的,需要比较的次数特别多,所以一个有效的解决方案就是做一个规定,当无论发生任何变化时,开发者都要返回一个新的对象,没有变化时,开发者返回就的对象,这也就是 redux 为什么要把 reducer 设计成纯函数的原因。
受控与非受控组件
受控组件用value和组件的state绑定,当value更新时,会自动更新state 非受控组件没有value,采用ref直接操作dom
react-router
可以实现无刷新的条件下切换显示不同的页面。路由的本质就是页面的URL发生改变时,页面的显示结果可以根据URL的变化而变化,但是页面不会刷新。通过前端路由可以实现单页(SPA)应用 React-router4.0中的React-router-dom包来介绍常用的BrowserRouter、HashRouter、Link和Router等。
- BrowserRouter>用BrowserRouter> 组件包裹整个App系统后,就是通过html5的history来实现无刷新条件下的前端路由。与BrowserRouter>对应的是HashRouter>,HashRouter>使用url中的hash属性来保证不重新刷新的情况下同时渲染页面。
- Route> 组件十分重要,Route> 做的事情就是匹配相应的location中的地址,匹配成功后渲染对应的组件。
- Route>定义了匹配规则和渲染规则,而 决定的是如何在页面内改变url,从而与相应的Route>匹配。Link>类似于html中的a标签,此外Link>在改变url的时候,可以将一些属性传递给匹配成功的Route,供相应的组件渲染的时候使用。
react-router 在history模式中push和replace有什么区别
- pushState和repalce的相同点:就是都会改变当前页面显示的url,但都不会刷新页面。
- push(''):添加一个新的记录到历史堆栈, history.length+1。(一般会用来跳转到一个新页面, 用户点击浏览器的回退按钮可以回到之前的路径。)
- replace(''):替换掉当前堆栈上的记录, history.length不变。
React-Router 4中Router组件有几种类型?
- 利用了pushState和replaceState来构建路由。pushState和replaceState是HTML5新接口,可以改变网址(存在跨域限制)而不刷新页面,这个强大的特性后来用到了单页面应用
- 使用window.location.hash和hashchange事件构建路由。
React-Router 4的switch有什么用
Switch排他性路由,采用 Switch,只有一个路由会被渲染,并且总是渲染第一个匹配到的组件,更好进行路由匹配
react-router4怎么在路由变化时重新渲染同一个组件?
- 在同一个组件添加不同的key,以下重新封装了组件
- 可以在这个组件的componentWillReceiveProps和shouldComponentUpdate生命周期方法中添加url变化的判断,如果url判断变化,就执行相关的逻辑代码(变化了就会就会重新执行render()函数,组件变会进行重新渲染。)
Mobx和Redux有什么区别
- 两者对比:
redux将数据保存在单一的store中,mobx将数据保存在分散的多个store中 redux使用plain object保存数据,需要手动处理变化后的操作;mobx适用observable保存数据,数据变化后自动处理响应的操作 redux使用不可变状态,这意味着状态是只读的,不能直接去修改它,而是应该返回一个新的状态,同时使用纯函数;mobx中的状态是可变的,可以直接对其进行修改 mobx相对来说比较简单,在其中有很多的抽象,mobx更多的使用面向对象的编程思维;redux会比较复杂,因为其中的函数式编程思想掌握起来不是那么容易,同时需要借助一系列的中间件来处理异步和副作用 mobx中有更多的抽象和封装,调试会比较困难,同时结果也难以预测;而redux提供能够进行时间回溯的开发工具,同时其纯函数以及更少的抽象,让调试变得更加的容易
- 场景辨析: 基于以上区别,我们可以简单得分析一下两者的不同使用场景. mobx更适合数据不复杂的应用: mobx难以调试,很多状态无法回溯,面对复杂度高的应用时,往往力不从心. redux适合有回溯需求的应用: 比如一个画板应用、一个表格应用,很多时候需要撤销、重做等操作,由于redux不可变的特性,天然支持这些操作. mobx适合短平快的项目: mobx上手简单,样板代码少,可以很大程度上提高开发效率. 当然mobx和redux也并不一定是非此即彼的关系,你也可以在项目中用redux作为全局状态管理,用mobx作为组件局部状态管理器来用.
vue和react的区别
- 都支持服务器端渲染
- 都有Virtual DOM(虚拟dom),组件化开发,都有’props’的概念,这是properties的简写。props在组件中是一个特殊的属性,允许父组件往子组件传送数据,都实现webComponent规范
- 数据驱动视图
- 都有支持native的方案,React的React native,Vue的weex
- 构建工具:React和Vue都有自己的构建工具,你可以使用它快速搭建开发环境。React可以使用Create React App (CRA),而Vue对应的则是vue-cli。两个工具都能让你得到一个根据最佳实践设置的项目模板。都有管理状态,React有redux,Vue有自己的Vuex(自适应vue,量身定做)
- Vue和React通用流程:vue template/react jsx -> render函数 -> 生成VNode -> 当有变化时,新老VNode diff -> diff算法对比,并真正去更新真实DOM。
- 区别 设计思想
react
- 函数式思想,all in js ,jsx语法,js操控css
- 单项数据流
- setState重新渲染
- 每当应用的状态被改变时,全部子组件都会重新渲染。当然,这可以通过shouldComponentUpdate这个生命周期方法来进行控制,如果为true继续渲染、false不渲染,但Vue将此视为默认的优化。
vue
- 响应式思想,也就是基于数据可变的。把html、js、css、组合到一起,也可以通过标签引擎组合到一个页面中
- 双向绑定,每一个属性都需要建立watch监听(页面不用,涉及到组件更新的话需要)
- Vue宣称可以更快地计算出Virtual DOM的差异,这是由于它在渲染过程中,会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树
性能
- react ----大型项目:优化需要手动去做,状态可控
- vue ------中小型项目:状态改变需要watch监听,数据量太大的话会卡顿
- 模板的原理不同,这才是他们的本质区别:React是在组件JS代码中,通过原生JS实现模板中的常见语法,比如插值,条件,循环等,都是通过JS语法实现的,更加纯粹更加原生。而Vue是在和组件JS代码分离的单独的模板中,通过指令来实现的,比如条件语句就需要 v-if 来实现对这一点,这样的做法显得有些独特,会把HTML弄得很乱。举个例子,说明React的好处:react中render函数是支持闭包特性的,所以我们import的组件在render中可以直接调用。但是在Vue中,由于模板中使用的数据都必须挂在 this 上进行一次中转,所以我们import 一个组件完了之后,还需要在 components 中再声明下,
- Vue通过 getter/setter以及一些函数的劫持,能精确知道数据变化。React默认是通过比较引用的方式(diff)进行的,如果不优化可能导致大量不必要的VDOM的重新渲染。为什么React不精确监听数据变化呢?这是因为Vue和React设计理念上的区别,Vue使用的是可变数据,而React更强调数据的不可变,
- 响应式原理不同 Vue依赖收集,自动优化,数据可变。 Vue递归监听data的所有属性,直接修改。 当数据改变时,自动找到引用组件重新渲染。
React基于状态机,手动优化,数据不可变,需要setState驱动新的State替换老的State。 当数据改变时,以组件为根目录,默认全部重新渲染
- diff算法不同 不同的组件产生不同的 DOM 结构。当type不相同时,对应DOM操作就是直接销毁老的DOM,创建新的DOM。 同一层次的一组子节点,可以通过唯一的 key 区分。 但两者源码实现上有区别:
Vue基于snabbdom库,它有较好的速度以及模块机制。Vue Diff使用双向链表,边对比,边更新DOM。
React主要使用diff队列保存需要更新哪些DOM,得到patch树,再统一操作批量更新DOM。
- 渲染过程不同: Vue可以更快地计算出Virtual DOM的差异,这是由于它在渲染过程中,会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树。React在应用的状态被改变时,全部子组件都会重新渲染。通过shouldComponentUpdate这个生命周期方法可以进行控制,但Vue将此视为默认的优化。
- HoC 和 mixins 在 Vue 中我们组合不同功能的方式是通过 mixin,而在React中我们通过 HoC (高阶组件)。React 最早也是使用 mixins 的,不过后来他们觉得这种方式对组件侵入太强会导致很多问题,就弃用了 mixinx 转而使用 HoC,高阶组件本质就是高阶函数,React 的组件是一个纯粹的函数,所以高阶函数对React来说非常简单。但是Vue就不行了,Vue中组件是一个被包装的函数,并不简单的就是我们定义组件的时候传入的对象或者函数。
- 通信方式不同 props/事件, provide/inject ,vuex props/回调, context, redux
React 本身并不支持自定义事件,Vue中子组件向父组件传递消息有两种方式:事件和回调函数,而且Vue更倾向于使用事件。但是在 React 中我们都是使用回调函数的,这可能是他们二者最大的区别。
- Vuex 和 Redux 的区别 在 Vuex 中,store 来读取数据 在 Redux 中,我们每一个组件都需要显示的用 connect 把需要的 props 和 dispatch 连接起来。 另外 Vuex 更加灵活一些,组件中既可以 dispatch action 也可以 commit updates,而 Redux 中只能进行 dispatch,并不能直接调用 reducer 进行修改。 从实现原理上来说,最大的区别是两点: Redux 使用的是不可变数据,而Vuex的数据是可变的。Redux每次都是用新的state替换旧的state,而Vuex是直接修改 Redux 在检测数据变化的时候,是通过 diff 的方式比较差异的,而Vuex其实和Vue的原理一样,是通过 getter/setter来比较的(如果看Vuex源码会知道,其实他内部直接创建一个Vue实例用来跟踪数据变化)
JSX 防止注入攻击
React DOM 在渲染所有输入内容之前,默认会进行转义。它可以确保在你的应用中,永远不会注入那些并非自己明确编写的内容。所有的内容在渲染之前都被转换成了字符串。这样可以有效地防止 XSS(cross-site-scripting, 跨站脚本)攻击。
Babel 会把 JSX 转译成一个名为 React.createElement() 函数调用。
JSX相对于模版的优点:
- 灵活,因为本质是js
- 前端社区生态好 JSX相对于模版的缺点:
- 社区中很少做JSX的性能优化
- 自身几乎没有可编程性
- 太过于依赖宿主
this丢失
react中的事件绑定,onClick={this.handle}实际上是一个赋值操作,把App类中的handle函数,赋值给了onClick变量,当点击事件触发时,实际执行的是onClick()。
react中的onClick(),也不是JS中的原生点击事件,因为原生的事件函数中的this,应该是指向事件源dom对象的;而react中的onClick,是一个合成事件,在全局作用域中执行,没有调用者,所以内部的this是指向window的
又因为使用了class语法,默认采用了严格模式,所以this指向了undefined
segmentfault.com/a/119000004…