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 为组件做了两件事。
-
工具方法。这是 mixin 的基本功能,如果你想共享一些工具类方法,就可以定义它们,直 接在各个组件中使用。
-
生命周期继承,props 与 state 合并。这是 mixin 特别重要的功能,它能够合并生命周期方 法。如果有很多 mixin 来定义 componentDidMount 这个周期,那么 React 会非常智能地将它们都合并起来执行。同样,mixin 也可以作用在 getInitialState 的结果上,作 state 的 合并,而 props 也是这样合并的。
- ES6 Classes 与 decorator
decorator就是装饰器:
- 在组件类里面的方法使用:在其上一行加@函数名(3个参数和Object.defineProperty一样)
- 在组件类上使用:在其上一行加@类名(一个参数就是类实例)
- mixin 的问题
- 破坏了原有组件的封装
- 命名冲突
- 增加复杂性 针对这些问题,React 社区提出了新的方式来取代 mixin,那就是高阶组件
高阶组件
- higher-order function(高阶函数)在函数式 编程中是一个基本的概念,它描述的是这样一种函数:这种函数接受函数作为输入,或是输出一 个函数。比如,常用的工具方法 map 、 reduce 和 sort 等都是高阶函数
- 高阶组件(higher-order component),类似于高阶函数,它接受 React 组件作为输入,输出一 个新的 React 组件
高阶组件让我们的代码更具有复用性、逻辑性与抽象特性。 它可以对 render 方法作劫持,也可以控制 props 与 state
实现高阶组件的方法有如下两种。
- 属性代理(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;
- 反向继承(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>
);
}
}
在配置式组件内部,组件与组件间以及组件与业务间是紧密关联的,而我们需要完成的仅仅 是配置工作