前言
前端的框架轮子实在太多,因此这节课我就选一些具有代表性或者是使用广泛的讲讲,还有就是会讲讲一些框架通用的技术或者是思想。
平常在前端界里,大家一般讲的是“三大框架”:Vue, React, Angular. 不过 Angular 虽然背靠 Google 大佬,但目前在国内的使用量还算是比较低的,因此今天课程里 Angular 可能涉及不多(其实主要是我不会)。主要讲解的是 Vue 和 React,其他的几款框架大家也可回去自行了解(如Angular, Svelte, Preact, Ember, Solid)。由于后面 Vue 和 React 学姐们还会专门开课给大家讲,所以今天在这就不涉及过多语法层面的知识了(
Web Components 基础
首先我们讲一下 Web Components, Web Components 是一套原生 JS 组件化方案。其实组件化思想这块儿各个框架也都是相通的,学习这个也可以帮助你更好的理解 Vue/React 的相关源码。而作为 W3C 官方钦定的 API,那自然得是必学(
当我们的项目逐渐变得复杂起来,系统的各个部分将会变得难以维护,无论是为了使项目结构更加清晰,还是实现解耦,以及通过组件重用以精简多余代码等目的,组件化思想自然而然地就出现了。
这是我查资料时找到的最远古的有关前端组件的 W3C 页面:Behavioral Extensions to CSS ,没错,是 1999 年,上个世纪🤣,这时候 Chrome 都还没诞生,可以说前端组件化的历史是非常悠久了(不过实际定稿时间还是挺晚的)
因此组件化也并非是第三方框架才特有的特性,原生 JS 也是支持组件化的。虽然 Web Components 相对 Vue/React 来说很少单独使用,但是学习 Web Components 也是有利于培养我们的组件化思想的。
下面是 React 和 Vue 对 Web Component 在官方文档所表述的自己和 Web Components 的关系:
React 和 Web Components 为了解决不同的问题而生。Web Components 为可复用组件提供了强大的封装,而 React 则提供了声明式的解决方案,使 DOM 与数据保持同步。两者旨在互补。作为开发人员,可以自由选择在 Web Components 中使用 React,或者在 React 中使用 Web Components,或者两者共存。
(React 官方中文文档)
你可能已经注意到 Vue 组件非常类似于自定义元素——它是 Web 组件规范 的一部分,这是因为 Vue 的组件语法部分参考了该规范。例如 Vue 组件实现了 Slot API 与 is attribute。
(Vue 3 官方中文文档)
几个 Web Components 相关的前端框架:
好了,既然都已经了解完了,那我们先试着来创建一个组件的类:
class KyingStarButton extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({mode: 'open'});
const button = document.createElement("button");
button.textContent = "超人!";
button.style.padding = '8px 16px'
button.style.background = '#00A1D6'
button.style.color = '#FFFFFF'
button.style.border = 'none';
button.style.cursor = 'pointer'
button.style.fontSize = '14px'
button.style.borderRadius = '2px'
button.addEventListener('click', () => alert('彳亍👍'));
shadow.appendChild(button);
}
}
然后创建对应的 custom element:
customElements.define('kying-star', KyingStarButton);
然后在 html 文件中加入对应代码:
<kying-star></kying-star>
由于 Web Components 在本节课主要只是起到一个引入的作用,一些延伸(如<slot>等)语法就不多讲了,对 Web Components 感兴趣的可以自已读一下 MDN 的文档。
JSX & Template
上面的代码是传统的 dom 写法,我们可以还通过模板字符串将其优化成类似 template 和 jsx 的写法:
class KyingStarButton extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({mode: 'open'});
const template = document.createElement("div");
const style = document.createElement('style');
style.textContent = `button {
padding: 8px 16px;
background: #00A1D6;
color: #FFFFFF;
border: none;
cursor: pointer;
font-size: 14px;
border-radius: 2px;
}`
template.innerHTML = `
<button onClick="alert('彳亍👍')"> 我的超人!</button>
`
shadow.appendChild(style);
shadow.appendChild(template);
}
}
单单复制这一个相同的组件没啥意思,我们给它添加一些自定义属性以便于复用:
class KyingStarButton extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({mode: 'open'});
const template = document.createElement("div");
const style = document.createElement('style');
style.textContent = `button {
padding: 8px 16px;
background: #00A1D6;
color: #FFFFFF;
border: none;
cursor: pointer;
font-size: 14px;
border-radius: 2px;
}`
template.innerHTML = `
<button onClick="alert('${this.getAttribute('say')}')">
${this.getAttribute('text')}
</button>
`
shadow.appendChild(style);
shadow.appendChild(template);
}
}
但是这样还有一些问题,比如:
-
在模板字符串内的HTML代码缺乏语法高亮;
-
存在太多的样板代码等。
所以接下来我们来引入一下 JSX & Template。
你可以简单地理解为 JSX 为 React 对 Javascript 的语法拓展,Template 为 Vue 对 HTML 的语法拓展。这两种是目前描述 UI 的较为常见的语法。实际上 Vue 对 JSX 也有一定的支持,Angular 则也是采用的模板语句,Svelte 采用的模板语句则更接近于 Web Components 的语法。
// jsx 示例
const App = ({list}) => (
<div className="list">
{list.map((item, index) => (
<p key={index}>Name: {item.name}, Score: {item.score}</p>
))}
</div>
);
<!-- template 示例 -->
<template>
<div class="list">
<p v-for="item in list">Name: {{item.name}}, Score: {{item.score}}</p>
</div>
</template>
状态管理(State Management)
当我们的项目进行组件化后,组件之间的数据传输又成了一个新的问题。因此 Flux 模式应运而生:
Flux
Flux是Facebook用户建立客户端Web应用的前端架构, 它通过利用一个单向的数据流补充了 React 的组合视图组件,这更是一种模式而非正式框架,你能够无需许多新代码情况下立即开始使用 Flux。
首先,Flux将一个应用分成四个部分:
- View: 视图层
- Action(动作):视图层发出的消息(比如mouseClick)
- Dispatcher(派发器):用来接收Actions、执行回调函数
- Store(数据层):用来存放应用的状态,一旦发生变动,就提醒Views要更新页面
Flux 的最大特点,就是数据的"单向流动"。
-
用户访问 View
-
View 发出用户的 Action
-
Dispatcher 收到 Action,要求 Store 进行相应的更新
-
Store 更新后,发出一个"change"事件
-
View 收到"change"事件后,更新页面
视图也许引起新的动作 Action,这个动作作为用户交互的响应将在整个系统传播:
Redux
Dan Abramov 在 React Europe 2015 上作了一场令人印象深刻的演示 Hot Reloading with Time Travel,之后 Redux 迅速成为最受人关注的 Flux 实现之一。
Redux 把自己标榜为一个“可预测的状态容器”,其实也是 Flux 里面“单向数据流”的思想,只是它充分利用函数式的特性,让整个实现更加优雅纯粹,使用起来也更简单。
Redux(oldState) => newState
Redux 是超越 Flux 的一次进化。
我们可以先通过对比 Redux 和 Flux 的实现来感受一下 Redux 带来的惊艳。
首先是 action creators,Flux 是直接在 action 里面调用 dispatch:
export function addTodo(text) {
AppDispatcher.dispatch({
type: ActionTypes.ADD_TODO,
text: text
});
}
Redux 把它简化成了这样:
export function addTodo(text) {
return {
type: ActionTypes.ADD_TODO,
text: text
};
}
这一步把 dispatcher 和 action 解藕了,很快我们就能看到它带来的好处。
接下来是 Store,这是 Flux 里面的 Store:
let _todos = [];
const TodoStore = Object.assign(new EventEmitter(), {
getTodos() {
return _todos;
}
});
AppDispatcher.register(function (action) {
switch (action.type) {
case ActionTypes.ADD_TODO:
_todos = _todos.concat([action.text]);
TodoStore.emitChange();
break;
}
});
export default TodoStore;
Redux 把它简化成了这样:
const initialState = { todos: [] };
export default function TodoStore(state = initialState, action) {
switch (action.type) {
case ActionTypes.ADD_TODO:
return { todos: state.todos.concat([action.text]) };
default:
return state;
}
同样把 dispatch 从 Store 里面剥离了,Store 变成了一个 Pure function:(state, action) => state
今天就先说到这里噜
😎