什么样的人生才是酷酷的一生
像我一样的人生
真正的学习通常发生在实践的过程中,为了了解而去进行的学习往往容易脱离真实环境,不够彻底。
新工作新业务的驱动,导致我需要重新学习 React 和 TypeScript,所以,这是我对 Vue 和 React 异同点的一次总结,我希望不仅仅局限于使用层面。
叙述结构按照组件化进行描述,即:异同点会在同一个话题进行总结。
Vue 是基于 Object.defineproperty
及观察者模式实现的双向绑定,包括对所有 “类 DOM 原生元素” 的数据流包装,各种双向控件的 v-model
就是一种表现。
前端框架里 Directive 的实现最早来自于 angular.js,但在以 angular.js 为代表的 MVC 时代,Controller 往往才是主角,相当于 Vue 中的组件的 Script 部分。
至于数据流的单双向问题,更多取决于开发者的业务实现,在 Vue2.0 之后,官方提倡优先以单向数据流为设计原则。
由于基于 Object.defineproperty
所以 Vue.js 有条件在核心就支持 computed 等相关数据动态计算功能,而 React 中是不存在的。
React 本身不存在整循环的数据绑定,它更专注于 State > View
的最优更新这件事。
所以,任何事件产生的数据 Input 需通过我们自组织的业务逻辑来完成 State 的更新,继而 Diff 计算后进行 View 更新; 并且,React 依然提供了允许我们手动管理 View 更新与否的能力,而在 Vue 中这一点是被内部自动优化的。
Vue 靠 Object.defineproperty
对象的拦截知道何时更新视图,React 靠 setState
的代理模式来做到同样的事。
假设丢弃或不使用 Vue 中 v-model
类似的 API,你会发现单事件流来说和 React 其实没什么区别。
Vue 的组件系统中,我们往往假设数据是可变的,所以也往往都是直接改变数据,正因为如此,我们可以得到最细粒度更新的效果。
React 提倡数据不可变,每一次我们应该创建一个新的 State 覆盖之前的,至于最终更新所需的资源靠 Diff 算法来优化。
两种拦截模式背后进行了怎样复杂的计算?
Vue 在不同环境下会降级为不同的异步 API 作为一个数据捕获周期,如 Promise.resolve().then
MutationObserver
setTimeout
。
React 根据版本不同,有稍微不同的更新策略,大致是维护一个状态队列,将所有状态的更改推进队列,按照不同的事务类型进行不同周期的批量更新。
So
state
等价于 Vue 中的 data
setState
等价于 Vue 中的 直接赋值
setState.callback
等价于 Vue 中的 $nextTick
render
等价于 Vue 中的 render/template
更详细可以参考官方文档。
// Vue 中的双向绑定
export default {
name: 'some-component',
template: `<input name="count" v-model="count" />`,
data() {
return {
count: 1
}
}
}
// Vue 中实现单向数据流
export default {
name: 'some-component',
template: `<input name="count" :value="count" @input="handleChange" />`,
data() {
return {
count: 1
}
},
methods: {
handleChange(event) {
this.count = event.target.value
}
}
}
// React 中实现单向数据流
class SomeComponent extends React.Component {
constructor() {
this.state = { count: 1 }
}
handleChange(event) {
this.setState({ count: event.target.value })
}
render() {
return (
<div>
<input value={this.state.count} onChange={this.handleChange} />
</div>
)
}
}
按照我的理解,Vue 对于原生 DOM 元素的原生事件都有进行一层包装,简单说就是将所有 dispatchEvent
转化为基于中介者模式的 $emit
,
最终导致了事件传播形式的统一,开发者只需要关注框架的事件派发标准就可以。
并且,Vue 中对于各 DOM 元素绑定的事件会被转换为真实 DOM 的事件绑定,顺序为:
TemplateEvent > DomEvent + Decorate > CompoentEvent
各种修饰符,就是在 “包装转化的这一层” 对事件进行了不同程度的:冒泡处理、拦截处理等等...
比如一个例子:
<!-- 事件捕获 + 停止传播 -->
<div class="box" @click.capture.stop="handleClick"></div>
<!-- 转换后的伪代码 -->
<script>
box.addEventListener(
"click",
(event) => {
event.stopPropagation();
handleClick(event);
},
false
);
</script>
在 React 中,为了兼顾浏览器兼容性、事件统一机制的高性能,React 内置了一套 SyntheticEvent 用于统一事件管理的系统。
简单说,从头到尾,React 只为最顶级元素绑定了一大套事件,我们为某某元素绑定的事件都相当于在 React 的 SyntheticEvent 里 “注册”; 当事件发生时,我们注册的事件会相应地被触发。
我们为 “类原生 DOM” 组件/元素 绑定的事件并不会真正地转化为真正的 Dom 事件,而是被挂载在 SyntheticEvent 系统的不同周期内进行触发和处理; 这些挂载的事件中得到的 Event 对象是经过 SyntheticEvent 包装后的复合对象,由原生事件产生,由 SyntheticEvent 加工得到的。
如果一定要得到原生事件属性,可通过 nativeEvent
访问得到;SyntheticEvent 支持大部分 Dom 原生事件,但不是全部;
如果需要支持 SyntheticEvent 之外的事件需要在 React 的生命周期中挂载和卸载事件,也就是原生的 window.addEventListener
。
由于 SyntheticEvent 是共享的。意味着在调用事件回调之后,SyntheticEvent 对象将会被重用,并且所有属性会被置空。这是出于性能因素考虑的。 因此,您无法以异步方式访问事件。
从使用层面看,这俩也没啥区别,hahaha~
<!-- Vue 中事件捕获 + 停止传播 -->
<div class="box" @click.capture.stop="handleClick"></div>
<!-- React 中事件捕获 + 停止传播 -->
<div class="box" onClickCapture={event => event.stopPropagation() && handleClick()}></div>
两者的通信机制基本类似。
复杂的跨组件通信,两者都有对应的 MessageBus/Store
的解决方案。
Vue 内置了基于中介者模式事件派发机制,使用 $emit
和 $on
作为事件额派发器和接收器。
在组件通信里,React 的事件系统并不能为其进行服务,所以往往是将 父组件的事件处理 以 props
的形式传递给子组件直接调用。
这两者的区别更多体现在事件传递,感觉 React 回调传递会导致组件间不能彻底解耦,当你为宿主组件编写处理逻辑时还需要了解子组件的 API、内部逻辑。
<!-- Vue 父子通信 -->
<template>
<div
class="child"
@click="$emit('click-children', 'i am child 1 event data')"
/>
</template>
<template>
<div class="parent">
<children @click-children="handleChildrenClick"></children>
</div>
</template>
<!-- React 父子通信 -->
<script>
function Child(props) {
return <div className="child" onClick={props.onChildClick}></div>;
}
class Parent extends React.Component {
constructor(props) {
super(props);
}
onChildClick(event) {
console.log("onChildClick", event);
}
render() {
return <Child onChildClick={this.onChildClick} />;
}
}
</script>
没仔细研究过,研究过也没看懂,所以,不知道。
数据 IO 的类型采用不同前缀体现,当然本质上也都是内置的 Directive。 Directive 提供了大部分 View 层所需的展示逻辑功能。
<!-- 数据传递: -->
<some-compnent :attrdata="someData" />
<some-compnent strattr="someStr" />
<!-- 事件绑定: -->
<some-compnent @event.decorate="eventHandle" />
<!-- 指令绑定: -->
<some-compnent v-some-directive:name="directiveAttr" />
<!-- 渲染逻辑: -->
<a-compnent v-if="isTrue" />
<b-compnent v-else />
<list-item-component v-for="item in list" :key="item.id"
>{{ item.name }}</list-item-component
>
<!-- 插值实例: -->
<div class="component-a">
<p>这是一个 A 组件</p>
<slot></slot>
</div>
<component-a>
<p>我是给 A 组件的内容</p>
</component-a>
<!-- output -->
<div class="component-a">
<p>这是一个 A 组件</p>
<p>我是给 A 组件的内容</p>
</div>
数据 IO 的类型统一采用花括号 {}
或属性值 ""
体现。
默认并不存在 Directive,View 层所需的展示逻辑功能都需要自行业务编码。
<!-- 数据传递: -->
<Compoent data="{someData}" title="stringTitle" />
<!-- 事件绑定: -->
<Compoent onEvent="{eventHandle}" />
<!-- 渲染逻辑:三目运算输出值、三目运算输出不同组件、输出循环列表 -->
<script>
render() {
return (
<div>User {isLoggedIn ? 'username logged in' : 'not login'}</div>
{ isLoggedIn ? (
<LogoutButton onClick={this.handleLogoutClick} />
) : (
<LoginButton onClick={this.handleLoginClick} />
)
}
<ul>
{ numbers.map((number) =>
<li key={number.toString()}>{number}</li>
)}
</ul>
)
}
</script>
<!-- 插值逻辑: -->
<script>
function Box(props) {
return (
<div className="box">
<div className="box-inner">{props.children}</div>
</div>
);
}
function App() {
return (
<Box>
<div className="login-box">
<p>登陆弹窗</p>
</div>
</Box>
);
}
</script>
Vue 使用 loader 来抽离和打包 CSS,使用 CSS3 属性选择器实现作用域隔离, 但 CSS3 隔离属性在编译期生成,所以会有一些不可逆的弊端,比如无法根据动态组件结构来实现动态的 deep scope。
如 Angular 就有大概 4-5 种方案来实现作用域隔离。
貌似 React 的 CSS 作用域方案都通过 All in js 实现。
其他待续...
2019-07-29 补充:
就 template 来说:
设计结果背后的设计目的
在 Vue 中,绝大部分抽象在 Vue 被表现为 filter、directive、event... 在 React 中,大部分抽象可能都会是函数,而对函数的设计和运用就至关重要了。
完。
怎么能像你一样优秀,大佬
回复:
少夸我,多编码
不是 all in js?
回复:
我瓦特了我辣鸡我改