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


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

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

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

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

欢迎star和fork~github


1、Mixin

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

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引入公共业务逻辑:redux

mixins:[someMixins]
复制代码

从上面的例子,咱们来总结如下mixin的缺点:数组

  • Mixin是能够存在多个的,是一个数组的形式,且Mixin中的函数是能够调用setState方法组件中的state的,所以若是有多处Mixin的模块中修改了相同的state,会没法肯定state的更新来源dom

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

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

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

2、HOC

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

一、举例几种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)
复制代码

二、HOC的缺点

HOC解决了Mixin的一些缺陷,可是HOC自己也有一些缺点:

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

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

(2)HOC是静态构建的

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

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

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这个生命周期,来实现渲染性能的优化。

4、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);
  
}
复制代码
相关文章
相关标签/搜索