|
@@ -26,7 +26,7 @@ const Button = React.createClass({
|
|
|
|
|
|
这需要你自己在组件中实现 `shouldComponentUpdate` 方法,只不过这个方法具体的工作由 `shallowCompare` 帮你完成,即浅比较。
|
|
这需要你自己在组件中实现 `shouldComponentUpdate` 方法,只不过这个方法具体的工作由 `shallowCompare` 帮你完成,即浅比较。
|
|
|
|
|
|
-再后来 `React` 为了避免开发者在组件中总是要写这样一段同样的代码,进而推荐使用 `React.PureComponent`,总之 `React` 在一步步的脱离 `mixins`,他们认为 `mixins` 在 `React` 生态系统中并不是一种好的模式(注意:并没有说 `mixins` 不好,仅仅针对 `React` 生态系统),观点如下:
|
|
|
|
|
|
+再后来 `React` 为了避免开发者在组件中总是要写这样一段同样的代码,进而推荐使用 `React.PureComponent`,总之 `React` 在一步步地脱离 `mixins`,他们认为 `mixins` 在 `React` 生态系统中并不是一种好的模式(注意:并没有说 `mixins` 不好,仅仅针对 `React` 生态系统),观点如下:
|
|
|
|
|
|
> 1、`mixins` 带来了隐式依赖
|
|
> 1、`mixins` 带来了隐式依赖
|
|
> 2、`mixins` 与 `mixins` 之间,`mixins` 与组件之间容易导致命名冲突
|
|
> 2、`mixins` 与 `mixins` 之间,`mixins` 与组件之间容易导致命名冲突
|
|
@@ -34,11 +34,11 @@ const Button = React.createClass({
|
|
|
|
|
|
具体大家可以查看这篇文章 [Mixins Considered Harmful](https://reactjs.org/blog/2016/07/13/mixins-considered-harmful.html)。不过 `HOC` 也并不是银弹,它自然带来了它的问题,有兴趣的同学可以查看这个视频:[Michael Jackson - Never Write Another HoC](https://www.youtube.com/watch?v=BcVAq3YFiuc),其观点是:**使用普通组件配合 `render prop` 可以做任何 HOC 能做的事情**。
|
|
具体大家可以查看这篇文章 [Mixins Considered Harmful](https://reactjs.org/blog/2016/07/13/mixins-considered-harmful.html)。不过 `HOC` 也并不是银弹,它自然带来了它的问题,有兴趣的同学可以查看这个视频:[Michael Jackson - Never Write Another HoC](https://www.youtube.com/watch?v=BcVAq3YFiuc),其观点是:**使用普通组件配合 `render prop` 可以做任何 HOC 能做的事情**。
|
|
|
|
|
|
-本篇文章不会过多讨论 `mixins` 和 `HOC` 谁好谁坏,就像技术本身就没有好坏之分,只有适合不适合。难道 `React` 和 `Vue` 这俩哥们儿不也是这样吗🙂。
|
|
|
|
|
|
+本篇文章不会过多讨论 `mixins` 和 `HOC` 谁好谁坏,就像技术本身就没有好坏之分,只有适合不适合。难道 `React` 和 `Vue` 这俩哥们儿不也是这样吗?
|
|
|
|
|
|
-`ok`,我们回到高阶组件,所谓高阶组件其实就是高阶函数啦,`React` 和 `Vue` 都证明了一件事儿:**一个函数就是一个组件**。所以组件是函数这个命题成立了,那高阶组件很自然的就是高阶函数,即一个返回函数的函数,我们知道在 `React` 中写高阶组件就是在写高阶函数,很简单,那是不是在 `Vue` 中实现高阶组件也同样简单呢?其实 `Vue` 稍微复杂,甚至需要你对 `Vue` 足够了解,接下来就让我们一块在 `Vue` 中实现高阶组件,在文章的后面会分析为什么同样都是 `函数就是组件` 的思想,`Vue` 却不能像 `React` 那样轻松的实现高阶组件。
|
|
|
|
|
|
+`ok`,我们回到高阶组件,所谓高阶组件其实就是高阶函数啦,`React` 和 `Vue` 都证明了一件事儿:**一个函数就是一个组件**。所以组件是函数这个命题成立了,那高阶组件很自然的就是高阶函数,即一个返回函数的函数,我们知道在 `React` 中写高阶组件就是在写高阶函数,很简单,那是不是在 `Vue` 中实现高阶组件也同样简单呢?其实 `Vue` 稍微复杂,甚至需要你对 `Vue` 足够了解,接下来就让我们一起在 `Vue` 中实现高阶组件,在文章的后面会分析为什么同样都是 `函数就是组件` 的思想,`Vue` 却不能像 `React` 那样轻松地实现高阶组件。
|
|
|
|
|
|
-也正因如此所以我们有必要在实现 `Vue` 高阶组件之前充分了解 `React` 中的高阶组件,看下面的 `React` 代码:
|
|
|
|
|
|
+因此我们有必要在实现 `Vue` 高阶组件之前充分了解 `React` 中的高阶组件,看下面的 `React` 代码:
|
|
|
|
|
|
```js
|
|
```js
|
|
function WithConsole (WrappedComponent) {
|
|
function WithConsole (WrappedComponent) {
|
|
@@ -57,7 +57,7 @@ function WithConsole (WrappedComponent) {
|
|
|
|
|
|
> 1、高阶组件(`HOC`)应该是无副作用的纯函数,且不应该修改原组件
|
|
> 1、高阶组件(`HOC`)应该是无副作用的纯函数,且不应该修改原组件
|
|
|
|
|
|
-可以看到 `WithConsole` 就是一个纯函数,它接收一个组件作为参数并返回了一个新的组件,在新组件的 `render` 函数中仅仅渲染了被包装的组件(`WrappedComponent`),并没有侵入式的修改它。
|
|
|
|
|
|
+可以看到 `WithConsole` 就是一个纯函数,它接收一个组件作为参数并返回了一个新的组件,在新组件的 `render` 函数中仅仅渲染了被包装的组件(`WrappedComponent`),并没有侵入式地修改它。
|
|
|
|
|
|
> 2、高阶组件(`HOC`)不关心你传递的数据(`props`)是什么,并且被包装组件(`WrappedComponent`)不关心数据来源
|
|
> 2、高阶组件(`HOC`)不关心你传递的数据(`props`)是什么,并且被包装组件(`WrappedComponent`)不关心数据来源
|
|
|
|
|
|
@@ -71,7 +71,7 @@ function WithConsole (WrappedComponent) {
|
|
|
|
|
|
## Vue 中的高阶组件
|
|
## Vue 中的高阶组件
|
|
|
|
|
|
-了解了这些,接下来我们就可以开始着手实现 `Vue` 高阶组件了,为了让大家有一个直观的感受,我仍然会使用 `React` 与 `Vue` 进行对比的讲解。首先是一个基本的 `Vue` 组件,我们常称其为被包装组件(`WrappedComponent`),假设我们的组件叫做 `BaseComponent`:
|
|
|
|
|
|
+了解了这些,接下来我们就可以开始着手实现 `Vue` 高阶组件了,为了让大家有一个直观的感受,我仍然会使用 `React` 与 `Vue` 进行对比地讲解。首先是一个基本的 `Vue` 组件,我们常称其为被包装组件(`WrappedComponent`),假设我们的组件叫做 `BaseComponent`:
|
|
|
|
|
|
**base-component.vue**
|
|
**base-component.vue**
|
|
|
|
|
|
@@ -225,7 +225,7 @@ export default function WithConsole (WrappedComponent) {
|
|
}
|
|
}
|
|
```
|
|
```
|
|
|
|
|
|
-那这样是不是可以了呢?依然不行,因为 `this.$props` 始终是空对象,这是因为这里的 `this.$props` 指的是高阶组件接收到的 `props`,而高阶组件没有声明任何 `props`,所以 `this.$props` 自然是空对象啦,那怎么办呢?很简单只需要将高阶组件的 `props` 设置与被包装组件的 `props` 相同即可了:
|
|
|
|
|
|
+那这样是不是可以了呢?依然不行,因为 `this.$props` 始终是空对象,这是因为这里的 `this.$props` 指的是高阶组件接收到的 `props`,而高阶组件没有声明任何 `props`,所以 `this.$props` 自然是空对象啦,那怎么办呢?很简单只需要将高阶组件的 `props` 设置成与被包装组件的 `props` 相同即可:
|
|
|
|
|
|
**hoc.js**
|
|
**hoc.js**
|
|
|
|
|
|
@@ -309,7 +309,7 @@ export default {
|
|
|
|
|
|

|
|

|
|
|
|
|
|
-上图中蓝色框是 `BaseComponent` 组件渲染的内容,是正常的。红色框是高阶组件渲染的内容,可以发现无论是具名插槽还是默认插槽全部丢失。其原因很简单,就是因为我们在高阶组件中没有将分发的插槽内容透传给被包装组件(`WrappedComponent`),所以我们尝试着修改高阶组件:
|
|
|
|
|
|
+上图中蓝色框是 `BaseComponent` 组件渲染的内容,是正常的。红色框是高阶组件渲染的内容,可以发现无论是具名插槽还是默认插槽,全部丢失。其原因很简单,就是因为我们在高阶组件中没有将分发的插槽内容透传给被包装组件(`WrappedComponent`),所以我们尝试着修改高阶组件:
|
|
|
|
|
|
**hoc.js**
|
|
**hoc.js**
|
|
|
|
|
|
@@ -342,7 +342,7 @@ function WithConsole (WrappedComponent) {
|
|
|
|
|
|
纳尼😱?我们发现,分发的内容确实是渲染出来了,不过貌似顺序不太对。。。。。。蓝色框是正常的,在具名插槽与默认插槽的中间是有分界线(`===========`)的,而红色框中所有的插槽全部渲染到了分界线(`===========`)的下面,看上去貌似具名插槽也被作为默认插槽处理了。这到底是怎么回事呢?
|
|
纳尼😱?我们发现,分发的内容确实是渲染出来了,不过貌似顺序不太对。。。。。。蓝色框是正常的,在具名插槽与默认插槽的中间是有分界线(`===========`)的,而红色框中所有的插槽全部渲染到了分界线(`===========`)的下面,看上去貌似具名插槽也被作为默认插槽处理了。这到底是怎么回事呢?
|
|
|
|
|
|
-想弄清楚这个问题,就回到了文章开始时我提到的一点,即你需要对 `Vue` 的实现原理有所了解才行,否则无解。接下来就从原理触发讲解如何解决这个问题。这个问题的根源在于:**`Vue` 在处理具名插槽的时候会考虑作用域的因素**。不明白没关系,我们一点点分析。
|
|
|
|
|
|
+想弄清楚这个问题,就回到了文章开始时我提到的一点,即你需要对 `Vue` 的实现原理有所了解才行,否则无解。接下来就从原理出发来讲解如何解决这个问题。这个问题的根源在于:**`Vue` 在处理具名插槽的时候会考虑作用域的因素**。不明白没关系,我们一点点分析。
|
|
|
|
|
|
首先补充一个提示:**`Vue` 会把模板(`template`)编译成渲染函数(`render`)**,比如如下模板:
|
|
首先补充一个提示:**`Vue` 会把模板(`template`)编译成渲染函数(`render`)**,比如如下模板:
|
|
|
|
|
|
@@ -370,7 +370,7 @@ var render = function() {
|
|
}
|
|
}
|
|
```
|
|
```
|
|
|
|
|
|
-想要查看一个组件的模板被编译后的渲染函数很简单,只需要在访问 `this.$options.render` 即可。观察上面的渲染函数我们发现普通的 `DOM` 是通过 `_c` 函数创建对应的 `VNode` 的。现在我们修改模板,模板中除了有普通 `DOM` 之外,还有组件,如下:
|
|
|
|
|
|
+想要查看一个组件的模板被编译后的渲染函数很简单,只需要访问 `this.$options.render` 即可。观察上面的渲染函数我们发现普通的 `DOM` 是通过 `_c` 函数创建对应的 `VNode` 的。现在我们修改模板,模板中除了有普通 `DOM` 之外,还有组件,如下:
|
|
|
|
|
|
```html
|
|
```html
|
|
<div>
|
|
<div>
|
|
@@ -482,7 +482,7 @@ function WithConsole (WrappedComponent) {
|
|
|
|
|
|

|
|

|
|
|
|
|
|
-这里的关键点除了你需要了解 `Vue` 处理 `slot` 的方式之外,你还要知道通过当前实例 `_self` 属性访问当实例本身,而不是直接使用 `this`,因为 `this` 是一个代理对象。
|
|
|
|
|
|
+这里的关键点除了你需要了解 `Vue` 处理 `slot` 的方式之外,你还要知道通过当前实例 `_self` 属性访问当前实例本身,而不是直接使用 `this`,因为 `this` 是一个代理对象。
|
|
|
|
|
|
现在貌似看上去没什么问题了,不过我们还忘记了一件事儿,即 `scopedSlots`,不过 `scopedSlots` 与 `slot` 的实现机制不一样,本质上 `scopedSlots` 就是一个接收数据作为参数并渲染 `VNode` 的函数,所以不存在 `context` 的概念,所以直接透传即可:
|
|
现在貌似看上去没什么问题了,不过我们还忘记了一件事儿,即 `scopedSlots`,不过 `scopedSlots` 与 `slot` 的实现机制不一样,本质上 `scopedSlots` 就是一个接收数据作为参数并渲染 `VNode` 的函数,所以不存在 `context` 的概念,所以直接透传即可:
|
|
|
|
|
|
@@ -518,7 +518,7 @@ function WithConsole (WrappedComponent) {
|
|
到现在为止,一个高阶组件应该具备的基本功能算是实现了,但这仅仅是个开始,要实现一个完整健壮的 `Vue` 高阶组件,还要考虑很多内容,比如:
|
|
到现在为止,一个高阶组件应该具备的基本功能算是实现了,但这仅仅是个开始,要实现一个完整健壮的 `Vue` 高阶组件,还要考虑很多内容,比如:
|
|
|
|
|
|
> 函数式组件中要使用 `render` 函数的第二个参数代替 `this`。
|
|
> 函数式组件中要使用 `render` 函数的第二个参数代替 `this`。
|
|
-> 以上我们只讨论了以纯对象形式存在的 `Vue` 组件,然而除了纯对象外还可以函数。
|
|
|
|
|
|
+> 以上我们只讨论了以纯对象形式存在的 `Vue` 组件,然而除了纯对象外还可以是函数。
|
|
> 创建 `render` 函数的很多步骤都可以进行封装。
|
|
> 创建 `render` 函数的很多步骤都可以进行封装。
|
|
> 处理更多高阶函数组件本身的选项(`而不仅仅是上面例子中的一个简单的生命周期钩子`)
|
|
> 处理更多高阶函数组件本身的选项(`而不仅仅是上面例子中的一个简单的生命周期钩子`)
|
|
|
|
|
|
@@ -529,7 +529,7 @@ function WithConsole (WrappedComponent) {
|
|
|
|
|
|
## 为什么在 Vue 中实现高阶组件比较难
|
|
## 为什么在 Vue 中实现高阶组件比较难
|
|
|
|
|
|
-前面说过要分析一下为什么在 `Vue` 中实现高阶组件比较复杂而 `React` 比较简单。这主要是二者的设计思想和设计目标不同,在 `React` 中写组件就是在写函数,函数拥有的功能组件都有。而 `Vue` 更像是高度封装的函数,在更高的层面 `Vue` 能够让你轻松的完成一些事情,但与高度的封装相对的就是损失一定的灵活,你需要按照一定规则才能使系统更好的运行。
|
|
|
|
|
|
+前面说过要分析一下为什么在 `Vue` 中实现高阶组件比较复杂而 `React` 比较简单。这主要是二者的设计思想和设计目标不同,在 `React` 中写组件就是在写函数,函数拥有的功能组件都有。而 `Vue` 更像是高度封装的函数,在更高的层面 `Vue` 能够让你轻松的完成一些事情,但与高度的封装相对的就是损失一定的灵活,你需要按照一定规则才能使系统更好地运行。
|
|
|
|
|
|
有句话说的好:
|
|
有句话说的好:
|
|
|
|
|