react系列知识---11组件间抽象

361 阅读5分钟

mixin

  • mixin :将一个模块混入到一个另一个模块中,或是一个 类中
  • 封装 mixin 方法
const mixin = function (obj, mixins) {
    const newObj = obj;
    <!--复制原型对象-->
    newObj.prototype = Object.create(obj.prototype);
    for (let prop in mixins) {
    <!--判断是否是自身属性,而非继承属性-->
        if (mixins.hasOwnProperty(prop)) {
            newObj.prototype[prop] = mixins[prop];
        }
    }
    return newObj;
}
const BigMixin = {
    fly: () => {
        console.log('I can fly');
    }
};
const Big = function () {
    console.log('new big');
};
const FlyBig = mixin(Big, BigMixin);
const flyBig = new FlyBig(); // => 'new big'
flyBig.fly(); // => 'I can fly'
  • 在 React 中使用 mixin

React 在使用 createClass 构建组件时提供了 mixin 属性,比如官方封装的 PureRenderMixin :

import React from 'react';
import PureRenderMixin from 'react-addons-pure-render-mixin';
React.createClass({
    mixins: [PureRenderMixin],
    render() {
        return <div>foo</div>;
    }
});

使用 createClass 实现的 mixin 为组件做了两件事。

  1. 工具方法。这是 mixin 的基本功能,如果你想共享一些工具类方法,就可以定义它们,直 接在各个组件中使用。

  2. 生命周期继承,props 与 state 合并。这是 mixin 特别重要的功能,它能够合并生命周期方 法。如果有很多 mixin 来定义 componentDidMount 这个周期,那么 React 会非常智能地将它们都合并起来执行。同样,mixin 也可以作用在 getInitialState 的结果上,作 state 的 合并,而 props 也是这样合并的。

  • ES6 Classes 与 decorator

decorator就是装饰器:

  • 在组件类里面的方法使用:在其上一行加@函数名(3个参数和Object.defineProperty一样)
  • 在组件类上使用:在其上一行加@类名(一个参数就是类实例)
  1. 参考1

  2. 参考2

  • mixin 的问题
    • 破坏了原有组件的封装
    • 命名冲突
    • 增加复杂性 针对这些问题,React 社区提出了新的方式来取代 mixin,那就是高阶组件

高阶组件

  • higher-order function(高阶函数)在函数式 编程中是一个基本的概念,它描述的是这样一种函数:这种函数接受函数作为输入,或是输出一 个函数。比如,常用的工具方法 map 、 reduce 和 sort 等都是高阶函数
  • 高阶组件(higher-order component),类似于高阶函数,它接受 React 组件作为输入,输出一 个新的 React 组件

高阶组件让我们的代码更具有复用性、逻辑性与抽象特性。 它可以对 render 方法作劫持,也可以控制 props 与 state

实现高阶组件的方法有如下两种。

  1. 属性代理(props proxy)。高阶组件通过被包裹的 React 组件来操作 props。

定义高阶组件:

import React, {Component} from 'React';
const MyContainer = (WrappedComponent) => class extends Component {
    render() {
        return <WrappedComponent {...this.props}/>;
    }
}

使用高阶组件:

import React, {Component} from 'React';
class MyComponent extends Component {
    // ...
}
export default MyContainer(MyComponent);

用 decorator 来转换:

import React, {Component} from 'React';
@MyContainer
class MyComponent extends Component {
    render() {}
}
export default MyComponent;
  1. 反向继承(inheritance inversion)。高阶组件继承于被包裹的 React 组件。
const MyContainer = (WrappedComponent) => 
    class extends WrappedComponent {
        render() {
            return super.render();
        }
}

高阶组件返回的组件继承于 WrappedComponent。因为被动地继承了 WrappedCom- ponent ,所有的调用都会反向,这也是这种方法的由来

组合式组件开发实践

使用 React 开发组件时利用 props 传递参数。也就是说,用参数来配置 组件是我们最常用的封装方式。在一般场景中,仅修改组件用于配置的 props,就可以满足需求。 但随着场景发生变化,组件的形态也发生变化时,我们就必须不断增加 props 去应对变化,此时 便会导致 props 的泛滥,而在扩展过程中又必须保证组件向下兼容,只增不减,使组件的可维护 性降低

  • 组件再分离

我们期望组件是没有冗余的,组件与组件间视图重叠的部分应当被抽离出来,形成颗 粒度更细小的原子组件,使组件组合充满更多的可能

比如:抽离组件

对于颗粒度最小的组件而言,我们希望它是纯粹的、木偶式的组件

对于 SelectInput 组件,其状态完全依赖传入的 props,包括 selectedItem (显示用户 所选项)、 isActive (当前下拉状态)、 onClickHeader (反馈下拉状态)以及 placeholder (下拉 框提示)。简要实现:

class SelectInput extends Component {
    static displayName = 'SelectInput';
    render() {
        const {selectedItem, isActive, onClickHeader, placeholder} = this.props;
        const {text} = selectedItem;
        return (
            <div>
                <div onClick={onClickHeader}>
                    <Input type="text" disabled value={text} placeholder={placeholder}/>
                    <Icon className={isActive} name="angle-down"/>
                </div>
            </div>
        );
    }
}

组件再次分离后,可以根据在现实中的组件形态对其进行任意组合,形成统一层,摆 脱在原有组件上扩展的模式,有效提高组件的灵活性

  • 逻辑再抽象

组件层面的抽象不仅仅只停留在界面上,组件中的相同交互逻辑和业务逻辑也应该进行抽 象。在组件中,同样贯穿着这种函数式思想,只是实现方式略有不同。现在基于高阶组件来完成 组件逻辑上的抽象:

// 完成 SearchInput 与 List 的交互
const searchDecorator = WrappedComponent => {
    class SearchDecorator extends Component {
        constructor(props) {
            super(props);
            this.handleSearch = this
                .handleSearch
                .bind(this);
        }
        handleSearch(keyword) {
            this.setState({data: this.props.data, keyword});
            this
                .props
                .onSearch(keyword);
        }
        render() {
            const {data, keyword} = this.state;
            return (<WrappedComponent
                {...this.props}
                data={data}
                keyword={keyword}
                onSearch={this.handleSearch}/>);
        }
    }
    return SearchDecorator;
}
// 完成 List 数据请求
const asyncSelectDecorator = WrappedComponent => {
    class AsyncSelectDecorator extends Component {
        componentDidMount() {
            const {url, params} = this.props;
            fetch(url, {params}).then(data => {
                this.setState({data});
            });
        }
        render() {
            return (<WrappedComponent {...this.props} data={this.state.data}/>);
        }
    }
    return AsyncSelectDecorator;
}

最终,我们既可以用 decorator 的方式叠加套用,也可以利用 compose 方法将高阶组件层层包 裹,将界面与逻辑完美地结合在一起:

const FinalSelector = compose(asyncSelectDecorator, searchDecorator, selectedItemDecorator)(Selector);
class SearchSelect extends Component {
    render() {
        return (
            <FinalSelector {...this.props}>
                <SelectInput/>
                <SearchInput/>
                <List/>
            </FinalSelector>
        );
    }
}

在配置式组件内部,组件与组件间以及组件与业务间是紧密关联的,而我们需要完成的仅仅 是配置工作