导读
前端发展从早期的切图仔标签到现在的工程化,发展之迅猛的同时也面临着与C/S程序一样的问题,也就是系统化、模块化、规范化,经历过前端百家齐鸣的盛世,js前端在市场的影响力也越来越大,云端化催动着大批C/S往B/S的趋势,虚拟dom及node生态让js工程师可以应用到桌面程序、移动原生应用、小程序、各种物流网嵌入式软件中,大有能用js编写的终会用js,连当时全球风靡的热播剧西部世界里头机器人的源码也有js,场景是这样的:剧中host(机器人)老鸨梅芙,突然发现了自己的身份,于是胁迫人类作业员,并招募其它host,计划逃出这个虚拟世界,却发现自己所有的行动仍然是代码编写出来的,于是就有了下面的剧照

我大js一统江湖啊,连人工智能也靠它写
好吧,以上有些吹水了。玩笑归玩笑,但是我还是想说一句话不管什么语言都要靠生态去推动,reactNative与wexx众多js跨端框架和Flutter这种究竟选哪个好,我个人观点比较看好js .逃)
应用程序的复杂程度越来越大,那么我们也有一个刚需需要面对如果对代码进行复用,在react和vue有一个Mixin(混入)这个术语,这篇我们来聊聊react中的mixin。
mixin
先让我们看看在react中是如何使用的
var SetIntervalMixin = {
componentWillMount: function() {
this.intervals = [];
},
setInterval: function() {
this.intervals.push(setInterval.apply(null, arguments));
},
componentWillUnmount: function() {
this.intervals.forEach(clearInterval);
}
};
var createReactClass = require('create-react-class');
var TickTock = createReactClass({
mixins: [SetIntervalMixin], // 使用 mixin
getInitialState: function() {
return {seconds: 0};
},
componentDidMount: function() {
this.setInterval(this.tick, 1000); // 调用 mixin 上的方法
},
tick: function() {
this.setState({seconds: this.state.seconds + 1});
},
render: function() {
return (
<p>
React has been running for {this.state.seconds} seconds.
</p>
);
}
});
ReactDOM.render(
<TickTock />,
document.getElementById('example')
);
从上面我们就可以看出该react是对mixins这个参数名对象进行了封装,用当前组件可以使用SetIntervalMixin对象里的参数对象,看起来好像只是两个对象拼装,我们来简单的实现下。
首先要定义一个名叫mixins的函数,它是用来做混入的,接收一个参数list.
// 混入函数
function mixin(mixins: Function[]) {
return (target: any) => {
// 判断混入list是否为空
if (!mixins.length) {
console.warn(`混入对象是个空的,它必须是需要有值的`, );
return;
}
mixins.forEach((mixinsItem: Function) => {
// 获取对象里元素描述符
let descriptors = handleGetOwnPropertyDescriptor(mixinsItem);
// 将混入集合注入到目标对象的原型链里
Object.keys(descriptors).forEach((key:string) => {
defineProperty(target.prototype, key, descriptors[key]);
})
})
}
}
然后我们就可以愉快的使用了。
// 主函数
@mixin([
timeTools,
weatherTool,
])
class Main {
run: Function;
handle: Function;
title: string;
}
完整代码如下:
const {
defineProperty,
getOwnPropertyDescriptor,
} = Object;
// 混入函数
function mixin(mixins: Function[]) {
return (target: any) => {
// 判断混入list是否为空
if (!mixins.length) {
console.warn(`混入对象是个空的,它必须是需要有值的`, );
return;
}
mixins.forEach((mixinsItem: Function) => {
// 获取对象里元素描述符
let descriptors = handleGetOwnPropertyDescriptor(mixinsItem);
// 将混入集合注入到目标对象的原型链里
Object.keys(descriptors).forEach((key:string) => {
defineProperty(target.prototype, key, descriptors[key]);
})
})
}
}
// 时间
const timeTools:any = {
title: '时间',
run() {
console.log(`${this.title}:进行输出`)
}
}
// 天气
const weatherTool:any = {
name: '天气',
handle() {
console.log(`${this.name}:进行输出`)
}
}
// 主函数
@mixin([
timeTools,
weatherTool,
])
class Main {
run: Function;
handle: Function;
title: string;
}
const app = new Main();
app.run(); // log->时间:进行输出
app.handle(); // log->天气:进行输出
// 获取对象描述符
function handleGetOwnPropertyDescriptor(obj: Function):object {
let descriptors = {};
let keys = Object.keys(obj);
keys.forEach(key => {
descriptors[key] = getOwnPropertyDescriptor(obj, key);
});
return descriptors;
}
react里的mixin如果组件拥有多个 mixins,且这些 mixins 中定义了相同的生命周期方法(例如,当组件被销毁时,几个 mixins 都想要进行一些清理工作),那么这些生命周期方法都会被调用的。使用 mixins 时,mixins 会先按照定义时的顺序执行,最后调用组件上对应的方法。和我们刚刚实现并没有对此处理,会覆盖调,大体实现思路是这样。
mixin的缺点
- 如果组件中含有多个mixin,不同的mixin中含有相同名字的非生命周期函数,React会抛出异常(不是后面的函数覆盖前面的函数)。
- 组件中含有多个mixin,不同的mixin中含有相同名字的生命周期函数,不会抛出异常,mixin中的相同的生命周期函数(除render方法)会按照createClass中传入的mixins数组顺序依次调用,全部调用结束后再调用组件内部的相同的声明周期函数。
- 组件中含有多个mixin,不同的mixin中的默认props或初始state中不存在相同的key值时,则默认props和初始state都会被合并。
- 组件中含有多个mixin,不同的mixin中默认props或初始state中存在相同的key值时,React会抛出异常。
HOC
因为mixin存在的问题,react也提出用HOC(高阶组件)的形式可进行替代,大概的思路就是定义一个HOC对象,将目标组件以参数形式传入,HOC相当于一个设计模式,例子如下:
// 此函数接收一个组件...
function withSubscription(WrappedComponent, selectData) {
// ...并返回另一个组件...
return class extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {
data: selectData(DataSource, props)
};
}
componentDidMount() {
// ...负责订阅相关的操作...
DataSource.addChangeListener(this.handleChange);
}
componentWillUnmount() {
DataSource.removeChangeListener(this.handleChange);
}
handleChange() {
this.setState({
data: selectData(DataSource, this.props)
});
}
render() {
// ... 并使用新数据渲染被包装的组件!
// 请注意,我们可能还会传递其他属性
return <WrappedComponent data={this.state.data} {...this.props} />;
}
};
}
HOC的出现可以解决这些问题:
- 高阶组件就是一个没有副作用的纯函数,各个高阶组件不会互相依赖耦合
- 高阶组件也有可能造成冲突,但我们可以在遵守约定的情况下避免这些行为
- 高阶组件并不关心数据使用的方式和原因,而被包裹的组件也不关心数据来自何处。高阶组件的增加不会为原组件增加负担。
HOC缺陷:
-
HOC需要在原组件上进行包裹或者嵌套,如果大量使用HOC,将会产生非常多的嵌套,这让调试变得非常困难。 -
HOC可以劫持props,在不遵守约定的情况下也可能造成冲突。
Hooks
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。一个简单例子看看它:
import React, { useState } from 'react';
function Example() {
// 声明一个叫 “count” 的 state 变量。
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}