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

栏目: IOS · Android · 发布时间: 6年前

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

  为了实现分离业务逻辑代码,实现组件内部相关业务逻辑的复用,在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-alpha

npm 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.clientTop
    let left= document.documentElement.clientLeft
    return{
        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);
  
}
复制代码

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Web Designer's Idea Book

The Web Designer's Idea Book

Patrick Mcneil / How / 2008-10-6 / USD 25.00

The Web Designer's Idea Book includes more than 700 websites arranged thematically, so you can find inspiration for layout, color, style and more. Author Patrick McNeil has cataloged more than 5,000 s......一起来看看 《The Web Designer's Idea Book》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

在线进制转换器
在线进制转换器

各进制数互转换器

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具