REACT

186 阅读14分钟

Dom/虚拟Dom

1.虚拟dom: js对象形式对dom的描述,要通过ReactDom.render()才能渲染到页面上

2.虚拟DOM不会进行排版与重绘操作,而真实DOM会频繁重排与重绘

3.虚拟dom可以跨平台,跨浏览器兼容

4.虚拟dom上的事件都是绑定在document上,17之后是在root上,由统一的事件处理程序做事件分发

5.jsx语法还需要babel进行转换

6.虚拟dom首次渲染速度慢,但性能更优,效率更高

react事件机制

React基于VitrualDom自己实现了一套自己的事件机制,自己模拟了事件冒泡和捕获的过程,并进行统一的分发,采用了事件代理,批量更新等方法

1.跨平台,跨浏览器兼容

2.方便统一在挂载和卸载时做处理

3.避免垃圾回收,免得对象频繁的创建和回收。用事件池(17之后取消,更好的兼容微前端)管理,使事件对象可以复用,当所有事件处理函数被调用之后,其所有属性都会被置空

与原生事件区别

1.命名方式: 小驼峰

2.事件处理函数语法: 字符串,函数

react生命周期变更,以及使用方式;为什么变化,为什么取消了那几个生命周期

v16.3去掉componentWillMount,componentWillReceiveProps,componentWillUpdate;新增static getDerivedStateFromProps(props, state),getSnapshotBeforeUpdate(prevProps, prevState)

  • 生命周期:set props & state --> componentWillMount(static getDerivedStateFromProps) --> render -->(getSnapshotBeforeUpdate)--> componentDidMount

  • propsUpdate:componentWillReceiveProps(static getDerivedStateFromProps) --> shouldComponentUpdate --> componentWillUpdate(去掉) --> render --> (getSnapshotBeforeUpdate)--> componentDidUpdate(prevProps, prevState, snapshot)

  • stateUpdate:shouldComponentUpdate --> componentWillUpdate(getSnapshotBeforeUpdate) --> render --> componentDidUpdate(prevProps, prevState, snapshot)

  • 销毁:componentWillUnmount

  • 强制重新渲染组件:forceUpdate()

  • 错误处理: 当渲染过程,生命周期,或子组件的构造函数中抛出错误时,componentDidCatch,static getDerivedStateFromError()

  1. componentWillMount是在组件挂载之前调用,在此方法中调用setstate是不会触发render的,如果是初始化数据尽量写在constructor中,如果是请求数据,建议放在componentDidMount中

  2. componentWillReceiveProps:1.如果需要执行副作用(数据提取或动画),则建议在componentDidUpdate;2.如果是为了props更改时重新计算,请使用 memoization helper(memoize) 代替;3.如果是为了props更改时更改state,请考虑组件完全受控或使用key使组件完全不受控

  3. componentWillUpdate:因为props或者state更新而引起多次调用,而componentDidUpdate就只会被调用一次;不能在此方法中调用setstate

  4. getDerivedStateFromProps 会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。

  5. getSnapshotBeforeUpdate() 在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期的任何返回值将作为参数传递给 componentDidUpdate()。此用法并不常见,但它可能出现在 UI 处理中,如需要以特殊方式处理滚动位置的聊天线程等

Hook

为什么引入hook: 状态逻辑,副作用,数据请求等不相关的代码可能会组合写在一个生命周期内,十分混乱,容易引入bug,可读性和测试也变得困难,这就是为什么react要单独引入redux来进行状态管理,Hooks允许您根据相关的部分(例如设置订阅或获取数据)将一个组件拆分为更小的函数,而不是基于生命周期方法强制拆分;不再需要纠结this了

是react 16.8的新特性,Hook 是一个特殊的函数,你可以不用写class就使用state以及react的其他特性;只能在函数最顶层调用 Hook。不要在循环、条件判断或者子函数中调用;只能在 React 的函数组件中调用 Hook。Hook 使用了 JavaScript 的闭包机制;Hook 为已知的 React 概念提供了更直接的 API:props, state,context,refs 以及生命周期

  • useEffect:有点类似生命周期,给函数组件执行副作用(数据获取,设置订阅以及手动更改 React 组件中的 DOM 都属于副作用)。可以看成是 class 组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 的组合。会在每次渲染后都执行,如果return 一个函数,则可以执行清除操作;如果传入第二个参数,则可以在指定值变化时再执行effect;该 Hook 接收一个包含命令式、且可能有副作用代码的函数。

  • useState:没有this,参数不一定为对象,跟setState功能相同,返回值为当前state和更新他的函数

  • useReducer:通过 reducer 来管理组件本地的复杂 state

    useState的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。(如果你熟悉 Redux 的话,就已经知道它如何工作了。)

    在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为你可以向子组件传递 dispatch 而不是回调函数

  • useCallback:缓存函数,使得父组件更新时不用重新创建,避免重复渲染

  • useMemo:缓存复杂计算结果,避免组件更新时,重新计算

  • useContext:不使用组件嵌套就可以订阅 React 的 Context;

  • useRef:useRef 就像是可以在其 .current 属性中保存一个可变值的“盒子”

那么 React 怎么知道哪个 state 对应哪个 useState?答案是 React 靠的是 Hook 调用的顺序。

setState

  • 调用setstate时,react.js不会马上修改state,而是先把这个对象放到一个更新队列中,合并之后然后再触发state和组件更新(批处理机制),在componentDidMount 和componentDidUpdate中统一更新

  • isBatchingUpdates,判断是直接更新还是先暂存放入dirtyComponent队列,setState 只在合成事件和钩子函数中是“异步”的,在原生事件和 setTimeout 中都是同步的,

  • setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次 setState , setState 的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新

fiber

Fiber是一个js对象(本质是链表),能承载节点信息、优先级、updateQueue,同时它还是一个工作单元。fiber是双缓存的,真实dom对应在内存中的Fiber节点会形成Fiber树,这颗Fiber树在react中叫current Fiber,也就是当前dom树对应的Fiber树,而正在构建Fiber树叫workInProgress Fiber,这两颗树的节点通过alternate相连.

  1. Fiber双缓存可以在构建好wip Fiber树之后切换成current Fiber,内存中直接一次性切换,提高了性能
  2. Fiber的存在使异步可中断的更新成为了可能,作为工作单元,可以在时间片内执行工作,没时间了交还执行权给浏览器,下次时间片继续执行之前暂停之后返回的Fiber
  3. Fiber可以在reconcile的时候进行相应的diff更新,让最后的更新应用在真实节点上

关于优先级:事件(离散事件click,阻塞事件scroll,连续事件audio的canplay)优先级/更新优先级/任务优先级

diff,key

  1. React diff 与 传统diff 的不同是 React通过优化将复杂度O(n^3)提升至O(n)

  2. React 通过三个方面对tree diff, component diff, element diff 进行了优化

  3. 在开发时,尽量保持稳定的DOM结构,并且减少将最后的节点移动到首部的操作,能够优化渲染性能

  • 单节点diff

    key和type相同,可以复用

    key不同,直接删除

    key相同,type不同,标记删除该节点和兄弟节点,重新创建节点

  • 多节点diff

    多节点diff有3个遍历,第一个遍历处理节点的更新(props,type(标签)更新和删除),第二个处理节点的新增,第三个处理节点位置改变

render

render阶段的主要工作是构建Fiber树和生成effectList,

1.新创建的workInProgress fiber

2.performUnitOfWork:workInProgress fiber和会和已经创建的Fiber连接起来形成Fiber树。这个过程类似深度优先遍历

  • 捕获阶段 从根节点rootFiber开始,遍历到叶子节点,每次遍历到的节点都会执行beginWork,并且传入当前Fiber节点,然后创建或复用它的子Fiber节点,并赋值给workInProgress.child。

  • 冒泡阶段 在捕获阶段遍历到子节点之后,会执行completeWork方法,执行完成之后会判断此节点的兄弟节点存不存在,如果存在就会为兄弟节点执行completeWork,当全部兄弟节点执行完之后,会向上‘冒泡’到父节点执行completeWork,直到rootFiber。

  • 首次调用会全部替换容器内部的子节点,后续调用则通过diff算法来进行更新

  • 不会替换容器节点,可以在不覆盖现有子节点的情况下,将组件插入已有的DOM节点中

  • ReactDOM.render() 目前会返回对根组件 ReactComponent 实例的引用。 但是,目前应该避免使用返回的引用,因为它是历史遗留下来的内容,而且在未来版本的 React 中,组件渲染在某些情况下可能会是异步的。 如果你真的需要获得对根组件 ReactComponent 实例的引用,那么推荐为根元素添加 callback ref

  • 使用 ReactDOM.render() 对服务端渲染容器进行 hydrate 操作的方式已经被废弃,并且会在 React 17 被移除。作为替代,请使用 hydrate()。

  1. 首先babel编译为React.createElement,返回legacyRenderSubtreeIntoContainer(父组件,子元素,根节点,forceHydrate协调更新(是否在服务端渲染),callback)

  2. 首次进来判断root,如果没有则创建根节点,创建legacyCreateRootFromDOMContainer并返回一个ReactSyncRoot实例,执行unbatchedUpdates(不需要批量更新,把执行上下文切换成非批量上下文)

    • ReactSyncRoot中createContainer创建fiberRoot(FiberNode)和rootFiber并相互引用
    • 创建完rootFiber之后,会将fiberRoot实例的current属性指向刚创建的rootFiber
    • 同时rootFiber的stateNode属性会指向fiberRoot实例,形成相互引用
  3. 正常更新时,执行updateContainer更新

redux/mobx

目的:解决组件之间的通信。如多级传递数据,中间组件会被迫接收和传递他们并不需要的props;解决兄弟组件传递数据需要先向上传递给父级,再往下传递的问题;rudex可以提供一个全局的store存放需要共享的数据;Redux DevTools 让你检查每一个 state 的变化

  • 使用场景:

    • 简单的,可以用内部state,或者hook
    • 有全局的东西需要共享,可以用context API
    • 有全局的东西,与应用的各独立部分都有交互,并且随着时间的推移,这个应用会越来越大,建议使用redux
  1. View,dispatch发出Action -> Store 自动调用 Reducer,并且传入两个参数:当前 State 和收到的 Action。 Reducer 会返回新的 State -> State 一旦有变化,Store 就会调用监听函数

  2. 中间件(middleware):Action 发出以后,过一段时间再执行 Reducer,这就是异步,需要用到中间件。在发出 Action 和执行 Reducer 这两步之间,添加了其他功能,其实就是对dispatch进行重构

    • 使用redux-thunk中间件,改造store.dispatch,使得后者可以接受函数作为参数

    • 另一种异步操作的解决方案,就是让 Action Creator 返回一个 Promise 对象。这就需要使用redux-promise中间件。

  3. connect: connect(mapStateToProps,mapDispatchToProps)(TodoList)

    • mapStateToProps:负责输入逻辑,即将state映射到 UI 组件的参数(props)。是一个函数,会订阅 Store,每当state更新的时候,就会自动执行,重新计算 UI 组件的参数,从而触发 UI 组件的重新渲染。

    • mapDispatchToProps:负责输出逻辑,建立 UI 组件的参数到store.dispatch方法的映射。

  4. Provider 组件:Provider的唯一功能就是传入store对象。connect方法生成容器组件以后,需要让容器组件拿到state对象,才能生成 UI 组件的参数,一种解决方法是将state对象作为参数,传入容器组件,React-Redux 提供Provider组件,可以让容器组件拿到state。它的原理是React组件的context属性。

import { Provider } from 'react-redux'
import { createStore } from 'redux'
import todoApp from './reducers'
import App from './components/App'

let store = createStore(todoApp);

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

5.Reducer 描述如何改变数据的纯函数,接受两个参数:当前state 和 action 传入的数据,通过运算得到新的 state。

redux-sage

redux-saga 是一个用于管理应用程序 Side Effect(副作用,例如异步获取数据,访问浏览器缓存等)的 library,它的目标是让副作用管理更容易,执行更高效,测试更简单,在处理故障时更容易。

可以想像为,一个 saga 就像是应用程序中一个单独的线程,它独自负责处理副作用。 redux-saga 是一个 redux 中间件,意味着这个线程可以通过正常的 redux action 从主应用程序启动,暂停和取消,它能访问完整的 redux state,也可以 dispatch redux action。

redux-saga 使用了 ES6 的 Generator 功能,让异步的流程更易于读取,写入和测试。 (如果你还不熟悉的话,这里有一些介绍性的链接  通过这样的方式,这些异步的流程看起来就像是标准同步的 Javascript 代码。(有点像 async/await,但 Generator 还有一些更棒而且我们也需要的功能)。

你可能已经用了 redux-thunk 来处理数据的读取。不同于 redux thunk,你不会再遇到回调地狱了,你可以很容易地测试异步流程并保持你的 action 是干净的

阻塞调用: take, put, call

非阻塞调用: select, takeEvery, takeLatest,

react-router 原理

引入了history库

  1. 使用BrowserRouter(源码在 react-router-dom 中,它是一个高阶组件),用H5的history库创建了一个专门的history,return Router,传递了history和children

  2. Router,设置了一个监听函数,使用的是history库的listen,setState({location}),向下传递history,location,match,staticContext

  3. 使用Route匹配的path,并渲染匹配的组件:Route 接受新的 nextContext 通过 matchPath 函数来判断 path 是否与 location 匹配,如果匹配则渲染,不匹配则不渲染

  4. 使用Link创建一个链接跳转到你想要渲染的组件:当点击页面的Link是,其实是点击的a标签,只不过使用了 preventDefault 阻止 a 标签的页面跳转;通过给a标签添加点击事件去执行 hitsory.push或者history.replace

dva

dva是一个数据流方案,适用于组件间通信多,业务复杂,需要引入状态管理的项目。基于redux 和redux-sage,内置react-router和fetch

umi

可扩展的企业级前端应用框架。Umi 以路由为基础的,同时支持配置式路由和约定式路由,保证路由的功能完备,并以此进行功能扩展。然后配以生命周期完善的插件体系,覆盖从源码到构建产物的每个生命周期,支持各种功能扩展和业务需求。

副作用 side Effect

与真实世界存在交互

redux和react-redux区别

1.创建方式不一样

redux和组件进行对接的时候是直接在组件中进行创建。

react-redux是运用Provider将组件和store对接,使在Provider里的所有组件都能共享store里的数据,还要使用connect将组件和react连接。

2.获取state的方式不一样

redux获取state是直接通过store.getState()。

react-redux获取state是通过mapStateToProps函数,只要state数据变化就能获取最新数据

3.触发action的方式不一样

redux是使用dispatch直接触发,来操作store的数据。

react-redux是使用mapDispathToProps函数然后在调用dispatch进行触发

react优化方式

componentDidMount 里不建议写 setState

componentWillReceiveProps-》getDerivedStateFormProps 派生state会使状态冗余

shouldComponentUpdate-》PureComponent/memo() 作浅比较

useCallBack(),useMemo()

useEffect 写依赖项

尽量少出现跨层级的操作

HOC

高阶组件是参数为组件,返回值为新组件的函数

1.withRouter react-router

2.connect redux