为了实现分离业务逻辑代码,实现组件内部相关业务逻辑的复用,在React的迭代中针对类组件中的代码复用依次发布了Mixin、HOC、Render props等几个方案。此外,针对函数组件,在React v16.7.0-alpha 中提出了hooks的概念,在本身无状态的函数组件,引入独立的状态空间,也就是说在函数组件中,也可以引入类组件中的state和组件生命周期,使得函数组件变得丰富多彩起来,此外,hooks也保证了逻辑代码的复用性和独立性。

  本文从针对类组件的复用解决方案开始说起,先后介绍了从Mixin、HOC到Render props的演进,最后介绍了React v16.7.0-alpha 中的 hooks以及自定义一个hooks

  • Mixin
  • HOC
  • Render props
  • React hooks的介绍以及如何自定义一个hooks

本文的原文地址发布在我的博客中:github.com/fortheallli…

欢迎star和fork~


一、Mixin

Mixin是最早出现的复用类组件中业务逻辑代码的解决方案,首先来介绍以下如何适应Mixin。下面是一个Mixin的例子:

const someMixins={printColor(){console.log(this.state.color);}setColor(newColor){this.setState({color:newColor})}componentDidMount(){..}
}复制代码

下面是一个使用Mixin的组件:

class Apple extends React.Component{//仅仅作为演示,mixins一般是通过React.createClass创建,并且ES6中没有这种写法mixins:[someMixins]constructor(props){super(props);this.state={color:'red'}this.printColor=this.printColor.bind(this);}render(){return <div className="m-box" onClick={this.printColor}>这是一个苹果</div>}
}
复制代码

在类中mixin引入公共业务逻辑:

mixins:[someMixins]
复制代码

从上面的例子,我们来总结以下mixin的缺点:

  • Mixin是可以存在多个的,是一个数组的形式,且Mixin中的函数是可以调用setState方法组件中的state的,因此如果有多处Mixin的模块中修改了相同的state,会无法确定state的更新来源

  • ES6 classes支持的是继承的模式,而不支持Mixins

  • Mixin会存在覆盖,比如说两个Mixin模块,存在相同生命周期函数或者相同函数名的函数,那么会存在相同函数的覆盖问题。

Mixin已经被废除,具体缺陷可以参考Mixins Considered Harmful

二、HOC

  为了解决Mixin的缺陷,第二种解决方案是高阶组件(high order component,简称HOC)。

1、举例几种HOC的形式

  HOC简单理解就是组件工厂,接受原始组件作为参数,添加完功能与业务后,返回新的组件。下面来介绍HOC参数的几个例子。

(1)参数仅为原始组件

const redApple = withFruit(Apple);
复制代码

(2)参数为原始组件和一个对象

const redApple = withFruit(Apple,{color:'red',weight:'200g'});
复制代码

但是这种情况比较少用,如果对象中仅仅传递的是属性,其实完全可以通过组件的props实现值的传递,我们用HOC的主要目的是分离业务,关于UI的展示,以及一些组件中的属性和状态,我们一般通过props来指定比较方便

(3)参数为原始组件和一个函数

const redApp=withFruit(App,()=>{console.log('I am a fruit')})
复制代码

(4)柯里化

最常见的是仅以一个原始组件作为参数,但是在外层包裹了业务逻辑,比如react-redux的conect函数中:

class Admin extends React.Component{}
const mapStateToProps=(state)=>{return {};
}
const mapDispatchToProps=(dispatch)=>{return {}
}const connect(mapStateToProps,mapDispatchToProps)(Admin)
复制代码

2、HOC的缺点

HOC解决了Mixin的一些缺陷,但是HOC本身也有一些缺点:

(1)难以溯源,且存在属性覆盖问题

  如果原始组件A,先后通过工厂函数1,工厂函数2,工厂函数3….构造,最后生成了组件B,我们知道组件B中有很多与A组件不同的props,但是我们仅仅通过组件B,并不能知道哪个组件来自于哪个工厂函数。同时,如果有2个工厂函数同时修改了组件A的某个同名属性,那么会有属性覆盖的问题,会使得前一个工厂函数的修改结果失效。

(2)HOC是静态构建的

  所谓静态构建,也就是说生成的是一个新的组件,并不会马上render,HOC组件工厂是静态构建一个组件,这类似于重新声明一个组件的部分。也就是说,HOC工厂函数里面的声明周期函数,也只有在新组件被渲染的时候才会执行。

(3)会产生无用的空组件

三、Render Prop

  Render Props从名知义,也是一种剥离重复使用的逻辑代码,提升组件复用性的解决方案。在被复用的组件中,通过一个名为“render”(属性名也可以不是render,只要值是一个函数即可)的属性,该属性是一个函数,这个函数接受一个对象并返回一个子组件,会将这个函数参数中的对象作为props传入给新生成的组件。

  这种方法跟直接的在父组件中,将父组件中的state直接传给子组件的区别是,通过Render Props不用写死子组件,可以动态的决定父组件需要渲染哪一个子组件。

或者再概括一点:

Render Props就是一个函数,做为一个属性被赋值给父组件,使得父组件可以根据该属性去渲染子组件。

(1)标准父子组件通信方法

  首先来看常用的在类组件中常用的父子组件,父组件将自己的状态state,通过props传递给子组件。

class Son extends React.Component{render(){const {feature} = this.props;return <div><span>My hair is {feature.hair}</span><span>My nose is {feature.nose}</span></div>}
}class FatherToSon extends React.Component{constructor(){this.state = {hair:'black',nose:'high'}}render(){return <Son feature = {this.state}>}
}
复制代码

  我们定义了父组件FatherToSon,存在自身的state,并且将自身的state通过props的方式传递给了子组件。

  这种就是常见的利用组件的props父子间传值的方式,这个值可以是变量,对象,也可以是方法,但是仅仅使用只能一次性的给特定的子组件使用。如果现在有个Daughter组件也想复用父组件中的方法或者状态,那么必须新构建一个新组件:

class FatherToDaughter extends React.Component{constructor(){this.state = {hair:'black',nose:'high'}}render(){return <Daughter feature = {this.state}>}
}
复制代码

从上面的例子可以看出通过标准模式的父子组件的通信方法,虽然能够传递父组件的状态和函数,但是无法实现复用。

(2)Render Props的引出

我们根据Render Props的特点:

Render Props就是一个函数,做为一个属性被赋值给父组件,使得父组件可以根据该属性去渲染子组件。

重新去实现上述的(1)中的例子。

class FatherChild extends React.Component{constructor(){this.state = {hair:'black',nose:'high'}}render(){<React.Fragment>{this.props.render}</React.Fragment>}
}
复制代码

此时如果子组件要复用父组件中的属性或者函数,则可以直接使用,比如子组件Son现在可以直接调用:

<FatherChild render={(obj)=>(<Son feature={obj}>)} />
复制代码

如果子组件Daughter要复用父组件的方法,可以直接调用:

<FatherChild render={(obj)=>(<Daughter feature={obj}>)} />
复制代码

  从这个例子中可以看出,通过Render Props我们实现同样实现了一个组件工厂,可以实现业务逻辑代码的复用,相比与HOC,Render Props有以下几个优点。

  • 不用担心props的命名问题
  • 可以溯源,子组件的props一定是来自于直接父组件
  • 是动态构建的

Render Props也有一个缺点:

就是无法利用SCU这个生命周期,来实现渲染性能的优化。

四、React hooks的介绍以及如何自定义一个hooks

  hooks概念在React Conf 2018被提出来,并将在未来的版本中被引入,hooks遵循函数式编程的理念,主旨是在函数组件中引入类组件中的状态和生命周期,并且这些状态和生命周期函数也可以被抽离,实现复用的同时,减少函数组件的复杂性和易用性。

  hooks相关的定义还在beta中,可以在React v16.7.0-alpha中体验,为了渲染hooks定义的函数组件,必须执行React-dom的版本也为v16.7.0-alpha,引入hooks必须先安装:

npm i -s React@16.7.0-alphanpm i -s React-dom@16.7.0-alpha
复制代码

  hooks主要有三部分组成,State Hooks、Effect Hooks和Custom Hooks,下面分别来一一介绍。

(1)State Hooks

  跟类组件一样,这里的state就是状态的含义,将state引入到函数组件中,同时类组件中更新state的方法为setState,在State Hooks中也有相应的更新状态的方法。

function ExampleWithManyStates() {// 声明各种state以及更新相应的state的方法const [age, setAge] = useState(42);const [fruit, setFruit] = useState('banana');const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);// ...
}
复制代码

  上述就声明了3个State hooks,相应的方法为useState,该方法创建一个传入初始值,创建一个state。返回一个标识该state的变量,以及更新该state的方法。

  从上述例子我们来看,一个函数组件是可以通过useState创建多个state的。此外State Hooks的定义必须在函数组件的最高一级,不能在嵌套,循环等语句中使用。

function ExampleWithManyStates() {// 声明各种state以及更新相应的state的方法if(Math.random()>1){const [age, setAge] = useState(42);const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);}else{const [fruit, setFruit] = useState('banana');const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);}// ...
}
复制代码

  上述的方式是不被允许的,因为一个函数组件可以存在多个State Hooks,并且useState返回的是一个数组,数组的每一个元素是没有标识信息的,完全依靠调用useState的顺序来确定哪个状态对应于哪个变量,所以必须保证使用useState在函数组件的最外层,此外后面要介绍的Effect Hooks的函数useEffect也必须在函数组件的最外层,之后会详细解释。

(2)Effect Hooks

  通过State Hooks来定义组件的状态,同样通过Effect Hooks来引入生命周期,Effect hooks通过一个useEffect的方法,以一种极为简化的方式来引入生命周期。 来看一个更新的例子:

import { useState, useEffect } from 'react';function Example() {const [count, setCount] = useState(0);useEffect(() => {document.title = `You clicked ${count} times`;});return (<div><p>You clicked {count} times</p><button onClick={() => setCount(count + 1)}>Click me</button></div>);
}
复制代码

上述就是一个通过useEffect来实现组件中生命周期的例子,useEffect整合了componentDidMount和componentDidUpdate,也就是说在componentDidMount和componentDidUpdate的时候都会执行一遍useEffect的函数,此外为了实现componentWillUnmount这个生命周期函数,useEffect函数如果返回值是一个函数,这个函数就被定义成在componentWillUnmount这个周期内执行的函数。

useEffect(() => {//componentDidMount和componentDidUpdate周期的函数体return ()=>{ //componentWillUnmount周期的函数体}
});
复制代码

如果存在多个useState和useEffect时,必须按顺序书写,定义一个useState后,紧接着就使用一个useEffect函数。

useState('Mary')
useEffect(persistForm)
useState('Poppins')
useEffect(updateTitle)
复制代码

因此通useState一样,useEffect函数也必须位于函数组件的最高一级。

(3)Effect Hooks的补充

上述我们知道useEffect其实包含了componentDidMount和componentDidUpdate,如果我们的方法仅仅是想在componentDidMount的时候被执行,那么必须传递一个空数组作为第二个参数。

useEffect(() => {//仅在componentDidMount的时候执行
},[]);
复制代码

上述的方法会仅仅在componentDidMount,也就是函数组件第一次被渲染的时候执行,此后及时状态更新,也不会执行。

此外,为了减少不必要的状态更新和渲染,可以如下操作:

useEffect(() => {//仅在componentDidMount的时候执行
},[stateName]);
复制代码

在上述的这个例子中,只有stateName的值发生改变,才会去执行useEffect函数。

(4)Custom Hooks自定义hooks

可以将useState和useEffect的状态和生命周期函数抽离,组成一个新的函数,该函数就是一个自定义的封装完毕的hooks。

这是我写的一个hooks ---> dom-location,

可以这样引入:

npm i -s dom-location
复制代码

并且可以在函数组件中使用。这个自定义的hooks也很简单,就是封装了状态和生命周期函数。

import { useState, useEffect } from 'react'const useDomLocation = (element) =>  {let [elementlocation,setElementlocation] = useState(getlocation(element));useEffect(()=>{element.addEventListener('resize',handleResize);return ()=>{element.removeEventListener('resize', handleResize);}},[]);function handleResize(){setElementlocation(getlocation(element));}function getlocation(E){let rect = E.getBoundingClientRect()let top = document.documentElement.clientToplet left= document.documentElement.clientLeftreturn{top    :   rect.top - top,bottom :   rect.bottom - top,left   :   rect.left - left,right  :   rect.right - left};}return elementlocation}
复制代码

然后直接在函数中使用:

import useDomLocation from 'dom-location';
function App() {....let obj = useDomLocation(element);}
复制代码

从Mixin到hooks,谈谈对React16.7.0-alpha中即将引入的hooks的理解相关推荐

  1. 谈谈基于OAuth 2.0的第三方认证 [上篇]

    对于目前大部分Web应用来说,用户认证基本上都由应用自身来完成.具体来说,Web应用利用自身存储的用户凭证(基本上是用户名/密码)与用户提供的凭证进行比较进而确认其真实身份.但是这种由Web应用全权负 ...

  2. [html] 谈谈你对input元素中readonly和disabled属性的理解

    [html] 谈谈你对input元素中readonly和disabled属性的理解 相同点:都会使文本框变成只读,不可编辑.不同点:1.disabled属性在将input文本框变成只读不可编辑的同时, ...

  3. 谈谈我对前端组件化中“组件”的理解,顺带写个Vue与React的demo

    谈谈我对前端组件化中"组件"的理解,顺带写个Vue与React的demo 前言 前端已经过了单兵作战的时代了,现在一个稍微复杂一点的项目都需要几个人协同开发,一个战略级别的APP的 ...

  4. 谈谈物哀与日本文化中的生死观

    谈谈物哀与日本文化中的生死观 大家在观看日本文学,动漫,电影的时候,不知道有没有发现有一个让我们国人看起来很奇怪,但是却被广泛的应用在主角身上的一个特征,主角大部分都不是双亲健在的,都是有一个或者多个 ...

  5. 谈谈考研刷题小程序中的卡片式设计

    谈谈考研刷题小程序中的卡片式设计 大家在UI设计中爱用卡片吗?无论是b端产品还是c端产品,卡片式设计几乎无处不在,卡片已然是我们在UI设计中最常见的样式了,今天我们就来一起详细的了解一下考研题库小程序 ...

  6. 在 React 项目中全量使用 Hooks

    目录 前言 React Hooks useState useReducer 基础用法 进阶用法 useContext useEffect useLayoutEffect useRef useImper ...

  7. mixin机制 vue_谈谈vue中mixin的一点理解

    谈谈vue中mixin的一点理解 vue中提供了一种混合机制--mixins,用来更高效的实现组件内容的复用.最开始我一度认为这个和组件好像没啥区别..后来发现错了.下面我们来看看mixins和普通情 ...

  8. 如何在React Native中使用React JS Hooks?

    In my articles, I'm going to be using either expo or snack online IDE and android emulator. 在我的文章中,我 ...

  9. react中函数式组件React Hooks

    React Hooks 函数式组件 使用hooks理由 高阶组件为了复用,导致代码层级复杂 生命周期的复杂 写成functional组件,无状态组件,因为需要状态,又改成了class,成本高 在16. ...

最新文章

  1. C#中的委托和事件 (4)---事件和委托的编译代码
  2. 隐私数据在隐私AI框架中的安全流动
  3. R语言负二项分布函数Negative Binomial Distribution(dnbinom, pnbinom, qnbinom rnbinom )实战
  4. SAP云平台和SAP传统Netweaver系统互联的技术方式
  5. 光纤收发器的选购原则介绍
  6. JavaWeb学习中的小问题
  7. HEVC之1 HM的使用
  8. R 语言的学习 —— 环境准备与安装
  9. 深入解析C/C++的优缺点以及就业方向
  10. Spring.net(一)----Spring.NET框架简介及模块说明
  11. 学习网络编程推荐安装的软件
  12. nicescroll.js滚动条错位
  13. 计算机需要无线网卡进行网络连,电脑连接无线网络时,提示"你没有首选无线网络"怎么办...
  14. micropython(esp8266)SG90舵机控制
  15. 移动通信网中的密码算法演进之完整性保护
  16. 一次性修改多张图片尺寸
  17. 负载均衡负载场景和解决方案
  18. android小气泡提示,小气泡六大常见问题详解!
  19. 【TARS】基于TARS的调试
  20. H264编码器12( H264基本原理 --图文表达很清晰)

热门文章

  1. 创造黑科技,守护新未来 | 360公司2019年春季校园招聘正式启动!
  2. Spring boot - 整合 Redis缓存(上)
  3. 使用Maven 创建web3.0项目
  4. Android ActionBar高级自定义——设置标题居中和添加控件
  5. spring@Autowired的对象为null,非容器中的类如何调用容器中的类
  6. 云函数与函数式编程思想结合会产生什么?
  7. Redis进阶-缓存问题
  8. Python可变参数、关键字参数及命名关键字参数
  9. JAVA无法加载此类文件,ORA-00376: 此时无法读取文件问题处理
  10. Redis数据结构之集合