1 前端开发的演变
静态页面阶段
- 后端收到浏览器的请求
- 生成静态页面
- 发送到浏览器
后端 MVC 模式:
- Model(模型层):提供/保存数据
- Controller(控制层):数据处理,实现业务逻辑
- View(视图层):展示数据,提供用户界面
AJAX 阶段
- AJAX 技术:脚本独立向服务器请求数据,拿到数据以后,进行处理并更新网页。整个过程中,后端只是负责提供数据,其他事情都由前端处理。
- 前端不再是后端的模板,而是实现了从“获取数据 -> 处理数据 -> 展示数据”的完整业务逻辑。
前端 MVC 阶段
基本上是把 MVC 模式搬到了前端。后来提出了 MVVM 模式:
- Model:读写数据
- View:展示数据
- View-Model:数据处理
View Model 是简化的 Controller。它的唯一作用就是为 View 提供处理好的数据,不含其他逻辑。也就是说,Model 拿到数据以后,View Model 将数据处理成视图层(View)需要的格式,在视图层展示出来。
这个模型的特点是 View 绑定 View Model。如果 View Model 的数据变了,View(视图层)也跟着变了;反之亦然,如果用户在视图层修改了数据,也立刻反映在 View Model。整个过程完全不需要手工处理。
SPA 阶段
前端可以做到读写数据、切换视图、用户交互,这意味着,网页其实是一个应用程序,而不是信息的纯展示。这种单张网页的应用程序称为 SPA(single-page application)。
所谓 SPA,就是指在一张网页(single page)上,通过良好的体验,模拟出多页面应用程序(application)。用户的浏览器只需要将网页载入一次,然后所有操作都可以在这张页面上完成,带有迅速的响应和虚拟的页面切换。
2 笔记
React
核心理念是将网页应用看成一个组件构成的状态机(state machine),状态的变化导致了 UI 的变化。React 本身的 API 并不多,是一个较为简单的框架。但是,要用好它,必须使用其他的配套工具,所以人们常说学习 React 并不是学习一个框架,而是学习一整套 React 技术栈。
React 本身的定位很单纯,它是一个网页组件的解决方案。也就是说,它只解决怎么把复杂的页面拆分成一个个组件,然后一个个独立的组件又怎么拼装成可以互相协同的网页。组件是中性的,任何一种应用架构都可以采用。因此,React 可以用于 MVC 架构,也可以用于 MVVM 架构,或者别的架构。
React 为了方便开发者自创了 JSX 语法。JSX 可以被 Babel 转码器转为正常的 JavaScript 语法。JSX 语法的特点就是,凡是使用 JavaScript 的值的地方,都可以插入这种类似 HTML 的语法。任何 JSX 表达式,顶层只能有一个标签,也就是说只能有一个根元素。一般来说,HTML 原生标签都使用小写,开发者自定义的组件标签首字母大写。
在我们的技术栈中 React 负责的是将数据渲染成页面,但是并不负责管理数据本身。
生命周期
组件有函数式组件和类组件两种,函数式组件没有生命周期函数,类组件必须继承React.Component
这个基类。
组件内部,所有参数都放在this.props
属性中。
this.props
对象有一个非常特殊的参数this.props.children
,表示当前组件“包裹”的所有内容。这个属性在 React 里面有很大的作用,它意味着组件内部可以拿到,用户在组件里面放置的内容。除了接受外部参数,组件内部也有不同的状态。React 规定,组件的内部状态记录在this.state
这个对象上面。
组件的运行过程中,存在不同的阶段。React 为这些阶段提供了钩子方法,允许开发者自定义每个阶段自动执行的函数。这些方法统称为生命周期方法(lifecycle methods)componentDidMount()
、componentWillUnmount()
和componentDidUpdate()
就是三个最常用的生命周期方法。
componentDidMount()
会在组件挂载后自动调用。componentWillUnmount()
会在组件卸载前自动调用。componentDidUpdate()
会在 UI 每次更新后调用(即组件挂载成功以后,每次调用 render 方法,都会触发这个方法)。
还有三个不常用的:
shouldComponentUpdate(nextProps, nextState)
:每当this.props
或this.state
有变化,在render
方法执行之前,就会调用这个方法。该方法返回一个布尔值,表示是否应该继续执行render
方法,即如果返回false
,UI 就不会更新,默认返回true
。组件挂载时,render
方法的第一次执行,不会调用这个方法。static getDerivedStateFromProps(props, state)
:该方法在render
方法执行之前调用,包括组件的第一次记载。它应该返回一个新的state
对象,通常用在组件状态依赖外部输入的参数的情况。getSnapshotBeforeUpdate()
:该方法在每次 DOM 更新之前调用,用来收集 DOM 信息。它返回的值,将作为参数传入componentDidUpdate()
方法。
受控组件
(非)受控组件:(不)能直接控制状态的组件。“受控”与“非受控”两个概念,区别在于这个组件的状态是否可以被外部修改。一个设计得当的组件应该同时支持“受控”与“非受控”两种形式,即当开发者不控制组件属性时,组件自己管理状态,而当开发者控制组件属性时,组件该由属性控制。而开发一个复杂组件更需要注意这点,以避免只有部分属性受控,使其变成一个半受控组件。一个典型的组件例子,可以参考 antd 中的 tabs 组件。
布局
典型布局
侧边导航
Umi.js
- 封装了编译步骤,包括了很多开发时的有用工具。只要你写好 React 代码,接下来 umi 就会把它处理为生产代码。
- 配置文件被约定为
config/config.js
。也可以使用.umirc.js
来作为配置文件。它和config/config.js
是二选一的。 src
目录,它用来存放项目的除了配置以及单测以外的主要代码。- 约定的存放页面代码的文件夹是
pages
。在配置项中添加singular
为true
可以让page
变为约定的文件夹。 - 在 umi 中,你可以使用约定式的路由,在
page
下面的 JS 文件都会按照文件名映射到一个路由。 - 除了约定式的路由,你也可以使用配置式的路由。其中
component
是一个字符串,它是相对于 page 目录的相对路径。 - 当有了 routes 的配置之后 umi 就不会再执行约定式对应的路由逻辑了。
脚手架
- 我们写的代码其实并不是原生的 JS,HTML 和 CSS,而是基于它们扩展出来的更上层的语法。
- 我们将会大量编写 React 组件(实际上 antd 就是 Ant Design 的 React 组件的实现),这些组件需要通过编译为最终的 JS 和 CSS,然后引入到 HTML 网页中才能够被浏览器正确地执行。
- 由于存在一个编译过程,这就需要基于编译工具搭建一个项目的脚手架,使得我们可以通过工具实现代码的编译。通过编译后的代码才是浏览器能够执行的代码,这样我们才能进行项目的开发和最终的部署。本项目使用 umi。
路由
在 umi 中,应用都是单页应用,页面地址的跳转都是在浏览器端实现的,不会去重新请求服务端获取 html。html 只是在应用初始化的时候加载一次。所有的页面都是由不同的组件构成,页面的切换其实就是不同组件的切换,你只需要在配置中把不同的路由路径和对应的组件关联上即可。
在 umi 应用中,路由的配置是在/config/config.js
中 exports.routes
中配置。
exports.routes
需要的是一个数组,数组中的每一个对象是一个路由信息。
配置代理
配置代理只需要在配置文件 config/config.js 中与 routes 同级处增加 proxy 字段,代码如下,
proxy: {
'/dev': {
target: 'https://08ad1pao69.execute-api.us-east-1.amazonaws.com',
changeOrigin: true,
},
},
配置的含义是:去往本地服务器 localhost:8000 的 ajax 调用中,如果是以 /dev
开头的,那么就转发到远端的 https://08ad1pao69.execute-api.us-east-1.amazonaws.com
服务器当中,/dev
也会保留在转发地址中。
比如:
/dev/random_joke
就会被转发到 https://08ad1pao69.execute-api.us-east-1.amazonaws.com/dev/random_joke
。
模拟服务端数据
在前面的章节中,我们设置了代理,于是所有的 HTTP 请求都可以先到达本地开发服务器,再被转发。在实际的开发中,后端的服务不一定马上可用,这就需要本地服务器另外一个能力:模拟数据(mock)。设置代理是 mock 的前提。
一个 ajax 请求发送到本地开发服务器后,我们可以设置:如果请求满足某个规则,则不转发这个请求,而是直接返回一个「假」结果给浏览器。在实际的开发中,我们常常先和服务端的同学商定 http 请求的接口接受什么参数,返回什么结果,然后先用 mock 数据来模拟,自己和自己「联调」。等待服务端同学开发好了,再解除 mock,用真实数据「联调」。
设置模拟数据时需要在工程根目录下的 mock 子目录中的建立文件。首先在工程中增加 mock 目录,并在其中创建文件 puzzlecards.js(取其他名字也可以,名字这里不需要)。如果想 mock 掉我们在上一个章节中的向 /dev/random_joke 的 ajax 调用,需要写入以下内容到文件,
const random_jokes = [
{
setup: "What is the object oriented way to get wealthy ?",
punchline: "Inheritance"
},
{
setup: "To understand what recursion is...",
punchline: "You must first understand what recursion is"
},
{
setup: "What do you call a factory that sells passable products?",
punchline: "A satisfactory"
}
];
let random_joke_call_count = 0;
export default {
"get /dev/random_joke": function(req, res) {
const responseObj =
random_jokes[random_joke_call_count % random_jokes.length];
random_joke_call_count += 1;
setTimeout(() => {
res.json(responseObj);
}, 3000);
}
};
模拟出错:
export default {
"get /dev/random_joke": function(req, res) {
res.status(500);
res.json