奇葩说框架之 react组件及逻辑复用

74edef997d397da78e59e2ec8ed09fbe.png

代码逻辑复用 是我们开发人员减少代码重复度,进行代码优化的一个重要因素,上期我们的同学分享了关于 Vue 框架的相关逻辑复用的方法及原理,本期我们一起来了解下 React 框架中的逻辑复用,涉及mixinsHOCrender propsHook 四个部分,由浅入深,欢迎大家交流学习。

1 mixins

1.1 mixins简介

e977cf2608a93e54b717344e545ae8e2.pngReact在v15.3.0之前,通过React.createClass 创建组件,支持mixins;在v15.3.0之后(包含),通过class 创建组件,废弃mixins。

1.2 mixins demo演示(基于v15.2.1)

6a363dbb46fb55cd38293151e6bcfca1.pngdemo比较简单,这里就不赘述了。

tips: 可以类比vue的mixins。

1.3 mixins 源码解析

基于1.2 demo,经过debugger调试,我画了如下基础的React相关的类图及调用关系。39a1d60e6c32f254106bd461125d8f1d.png蓝色部分就是核心的mixins流程,简单理解就是:

  1. 通过React.createClass(spec)接收到spec参数,判断spec对象中是否有mixins属性,

  2. 如果没有,那太棒了,直接遍历spec把propTypes、componentWillMount等生命周期函数、事件方法等挂载到Constructor.prototype上,这个Constructor就是react组件实例的构造函数;

  3. 如果有,就需要判断mixins对象中是否还有mixins属性(因为框架允许mixins嵌套mixins),没有再回到步骤2挂载到原型,有则继续步骤3遍历。

tips:步骤2挂载到原型,会经历react的合并策略,例如mixins、propTypes等规定我们可以定义多次,内部实现方法合并调用;render函数只被允许定义一次,超限会直接报错提示,更多可以查看源码了解

1.4 mixins 缺点

  • 引入了隐式依赖

例如:嵌套mixins各自定义的属性可能被嵌套的多个mixins隐式引用着,删除其中一个会破坏另一个,不形成层次结构,被扁平化并在相同的命名空间中运行。

  • 导致命名冲突

存在潜在的破坏性更改,因为在某些使用它的组件上可能已经存在具有相同名称的方法。

  • 导致滚雪球般的复杂性

原本抽离公共的mixins是为了代码复用,但是随着业务需求的迭代,逐渐出现了差异化,就开始在上面增加if...else逻辑,代码耦合度、复杂度增高,随之带来的是理解成本和开发出错成本,重构代码也是个难题。

详细理解可参看官方博客:Mixins Considered Harmful

2 HOC

2.1 HOC 简介

官方介绍:

高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。

具体而言,高阶组件是参数为组件,返回值为新组件的函数

2.2 HOC demo演示(基于v15.3.0)

高阶组件(HOC)常见的两个应用场景是属性代理和反向继承,因为对反向继承的应用理解不是太深入,所以这里仅演示对属性代理的操作。

c2459d039721334aa5c13d95c94aa9b7.png

Demo组件可理解为一个公共基础组件,Hoc函数定义返回了一个增强组件,抽象了props age属性,Hoc(Demo)的执行结果EnhancedComponent就是Demo的增强组件,当然可以将EnhancedComponent作为公共组件继续向上抽象公共属性和方法。

tips: 可以类比vue的高阶组件

2.3 HOC 源码解析

基于2.2 demo,画了如下基础的React相关的类图及调用关系。9fafe519d3885a8feb9eefa2a9618888.png

简单理解:

  1. 代码执行完 const EnhancedComponent = Hoc(Demo)可以通过浏览器看到EnhancedComponent变成了class HOC类,并且继承了React componente228231713ef06a16d0c5f7e8e226981.png

  2. 当代码执行遇到jsx语法const comp = <EnhancedComponent /> 时,会调用React.createElement()方法将其转成react element对象节点,如下图所示:8b2810e1c3c2287f37e25596f389f102.png

  3. 当代码执行ReactDOM.render(comp, document.getElementById("root")),其实是调用了ReactMount.render并最终返回了React  component类型的组件,ReactMount.render在执行过程中会执行HOC组件的render函数,在遇到jsx语法 return会执行步骤2转成react element,接着执行Demo组件的render函数,遇到jsx语法执行步骤1转成react element,最终经过复杂的渲染过程渲染到页面。

2.4 HOC 缺点

  • 很难复用逻辑,会导致组件树层级很深

如果使用HOC或者render props方案来实现组件之间复用状态逻辑,会很容易形成“嵌套地狱”。

  • 业务逻辑分散在组件的各个方法中

  • 难以理解的class

对于非前端专业的同学,理解class的成本会有点高

3 render props

3.1 render props 简介

官方介绍:

“render prop” 是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术。

具有 render prop 的组件接受一个函数,该函数返回一个 React 元素并调用它而不是实现自己的渲染逻辑。

注意:render prop 是因为模式才被称为 render prop ,你不一定要用名为 render 的 prop 来使用这种模式。事实上, 任何被用于告知组件需要渲染什么内容的函数 prop 在技术上都可以被称为 “render prop”

3.2 render props demo演示(基于v15.3.0)

e3465cf16c2c0625b342ff7f2fc27b3c.png

这里我定义了一个可以接收props func函数 的Demo组件,在外部使用Demo组件时传入func函数,告诉组件内部渲染成h1标签。

tips:这个用法简直像极了vue的slot插槽呀

3.3 render props 源码解析

1062c1fb85f2e72f2e622ecc47d201a1.png

简单理解:

  1. 当代码执行遇到jsx语法const comp = <Demo func={(name) => { return <h1>{name}</h1> }} />, 会调用React.js的createElement方法,其实调用的是ReactElement.js中createElement方法转成react element节点。

  2. 当代码执行ReactDOM.render(comp, document.getElementById("root"))调用的是ReactDOM.render,最终返回React component类型的组件,在执行render的过程中,会调用Demo组件的render函数,遇到jsx语法return (<div>{func(this.state.name)}</div>);会执行步骤1生成element节点并返回,当执行到func(this.state.name),遇到jsx语法return <h1>{name}</h1>, 会再次执行步骤1生成element节点并返回,最终经过复杂的渲染过程渲染到页面。

3.4 render props 缺点

缺点同高阶组件(HOC),嵌套地狱、业务逻辑分散在各个组件、难以理解的class。

上述三种逻辑复用策略都存在一定的问题,于是官方提出了Hook。

4 Hook

4.1 Hook 简介

官方介绍:

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

4.2 Hook demo演示(基于v16.8.0)

官方提供了好几个Hook方法,包括常用的useState、useEffect、useRef、useContext,还有高级的useReducer、useMemo、useLayoutEffect方法以及自定义Hook方法。

这里,我仅演示useState、useEffect68ec8a556c93f010ea51361f60470296.png

tips: 这个跟vue3 composition api神似

4.3 Hook 源码解析

在实际的debugger过程中会发现,useState的调用分为两个阶段:

声明阶段:在首次渲染页面的过程中会触发一次useState的调用,也就是下图蓝色区域的render阶段Reconciler过程中会触发,这个蓝色区域不是本次重点,后续会有同学分享到这部分哦~

0e7bf3c1fe95bc6d3bc030f875872666.png

其实这一次调用是在执行Demo组件中的这行代码:const [count, setCount] = useState(0);一路追踪useState,最终定位源码:

ReactCurrentDispatcher$1.current = nextCurrentHook === null ? HooksDispatcherOnMountInDEV : HooksDispatcherOnUpdateInDEV;

详细的调用关系图如下图蓝色部分:

1740eae7396baa3d69c677b7313316ab.png

可以看到声明阶段:实际调用的HooksDispatcherOnMountInDEV对象的useState,然后useState又调用的最终的核心方法-mountState

调用阶段:在更新count时,也就是调用setCount时会触发useState调用。

可以看到调用阶段:实际调用的HooksDispatcherOnUpdateInDEV对象的useState,然后useState又调用了updateState,最终调用了核心方法-updateReducer,接下来会重点分析这两个阶段核心的方法里mountStateupdateReducer都做了啥。

声明阶段-mountState

源码截图如下:2bec55bc3f385ba361146cde64c78ac6.png源码拆解如下图所示:500be964b92cb3ecdc26309e9ed12d98.png

简单理解:

创建hook对象,用于存放初始值和dispatch函数,便于调用阶段的数据更新,当执行 useState(0),实际上是执行 mountState(0),initialState即0赋值给memoizedState,dispatch关联内部dispatchAction函数, 最终返回[hook.memoizedState, dispatch],也是正好对应了[count, setCount]的解构赋值,最终count被赋上了初始值,setCount也关联上了内部定义的dispatchAction。

调用阶段-updateReducer

源码截图如下:d004e0664207f5562557233d0bdbdc0c.png51c8ed7300ee3a7ade55cced55a3b46c.png当我们点击主动触发setCount(count + 1)时,可以看下面流程图理解:3e600508cba27ba85f531d27052bd138.png最终执行的updateReducer,获取了声明阶段定义的hook对象,判断非首次更新则进入优化策略,是首次更新则会计算结果_newState的值,然后更新hook对象的新计算值memoizedState,初始值baseState、队列中记录的上一次更新的dispatch函数以及计算值,并最终返回[hook.memoizedState, dispatch],此时count值就变更成了1。

4.4 Hook 缺点

摘了几条核心的官方回复:

  • 没有计划移除class,推荐新代码尝试

  • 不推荐用Hook重写已有的class

  • 目标尽早覆盖class所有的使用场景

  • 不会替代render props和高阶组件

那么是不是可以理解Hook的2个小缺点:

  • 老的项目使用class不建议重构,实际问题就在于老项目

  • 使用场景可能还没有完全覆盖class的所有场景

5 总结

本文通过简介、demo使用、源码分析以及缺点分析了react的4种逻辑复用策略,学习过程中也发现了react和vue框架在实现逻辑复用思想上的雷同之处,很是惊喜。文章有不足之处,欢迎大家多多指出,共同分享学习!f227143d4a16c100bf9daec165fc9274.png

参考文献

[1].https://react.docschina.org/blog/2016/07/13/mixins-considered-harmful.html

[2].https://zh-hans.reactjs.org/docs/higher-order-components.html

[3].https://react.docschina.org/docs/render-props.html

[4].https://zh-hans.reactjs.org/docs/hooks-intro.html

[5].https://react.docschina.org/docs/hooks-faq.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值