本文介绍前端开发的历史和趋势,帮助大家了解 React 要解决什么问题。
互联网发展的早期,网站的前后端开发是一体的,即前端代码是后端代码的一部分。
那时的前端页面都是静态的,所有前端代码和前端数据都是后端生成的。前端只是纯粹的展示功能,脚本的作用只是增加一些特殊效果,比如那时很流行用脚本控制页面上飞来飞去的广告。
那时的网站开发,采用的是后端 MVC 模式。
前端只是后端 MVC 的 V。
2004年,AJAX 技术诞生,改变了前端开发。Gmail 和 Google 地图这样革命性的产品出现,使得开发者发现,前端的作用不仅仅是展示页面,还可以管理数据并与用户互动。
AJAX 技术指的是脚本独立向服务器请求数据,拿到数据以后,进行处理并更新网页。整个过程中,后端只是负责提供数据,其他事情都由前端处理。前端不再是后端的模板,而是实现了从“获取数据 --》 处理数据 --》展示数据”的完整业务逻辑。
就是从这个阶段开始,前端脚本开始变得复杂,不再仅仅是一些玩具性的功能。
前端代码有了读写数据、处理数据、生成视图等功能,因此迫切需要辅助工具,方便开发者组织代码。这导致了前端 MVC 框架的诞生。
2010年,第一个前端 MVC 框架 Backbone.js 诞生。它基本上是把 MVC 模式搬到了前端,但是只有 M (读写数据)和 V(展示数据),没有 C(处理数据)。因为,Backbone 认为前端 Controller 与后端不同,不需要、也不应该处理业务逻辑,只需要处理 UI 逻辑,响应用户的一举一动。所以,数据处理都放在后端,前端只用事件响应处理 UI 逻辑(用户操作)。
后来,更多的前端 MVC 框架出现。另一些框架提出 MVVM 模式,用 View Model 代替 Controller。MVVM 模式也将前端应用分成三个部分。
View Model 是简化的 Controller,所有的数据逻辑都放在这个部分。它的唯一作用就是为 View 提供处理好的数据,不含其他逻辑。也就是说,Model 拿到数据以后,View Model 将数据处理成视图层(View)需要的格式,在视图层展示出来。
这个模型的特点是 View 绑定 View Model。如果 View Model 的数据变了,View(视图层)也跟着变了;反之亦然,如果用户在视图层修改了数据,也立刻反映在 View Model。整个过程完全不需要手工处理。
前端可以做到读写数据、切换视图、用户交互,这意味着,网页其实是一个应用程序,而不是信息的纯展示。这种单张网页的应用程序称为 SPA(single-page application)。
所谓 SPA,就是指在一张网页(single page)上,通过良好的体验,模拟出多页面应用程序(application)。用户的浏览器只需要将网页载入一次,然后所有操作都可以在这张页面上完成,带有迅速的响应和虚拟的页面切换。
随着 SPA 的兴起,2010年后,前端工程师从开发页面(切模板),逐渐变成了开发“前端应用”(跑在浏览器里面的应用程序)。
目前,最流行的前端框架 Vue、Angular、React 等等,都属于 SPA 开发框架。
React 本身的定位很单纯,它是一个网页组件的解决方案。也就是说,它只解决怎么把复杂的页面拆分成一个个组件,然后一个个独立的组件又怎么拼装成可以互相协同的网页。
我们用React编写前端其实就是在编写一个个组件
按照功能划分,一张网页可以由多个互相独立的功能单位组成,这种功能单位就叫做“组件”(component)。比如,典型的网页分成页头、内容、页尾三个部分,就可以写成三个组件:Header、Content、Footer。这些组件拼装在一起,就构成了一张页面。
Header、Content、Sider3个组件拼接成了一个页面。Header、Content、Footer我们可以自己编写。当然为了提高开发效率,我们可以使用别人已经封装好了的组件库antd
组件的好处有很多,下面是其中几点。
React 的核心概念就是组件。
react的基础知识看官网react中文官网
React做的其实就是编写React组件,将React组件拼成页面,页面本质也是一个大的组件。
React组件分为两种:
//函数组件
function Welcome(props) {
return Hello, {props.name}
; //props是外界传给组件的属性
}
//类组件
class MyComponent extends React.Component{
render(){
return (
hello {this.props.name}
)
}
}
当编写简单的组件时可以用函数组件。当编写复杂的组件,如要用到生命周期、需要注入store、route时就用类组件。
编写组件的步骤:
import { Component } from 'react';
import { Layout } from 'antd'; //引入依赖
// Header, Footer, Sider, Content组件在Layout组件模块下
const { Header, Footer, Sider, Content } = Layout;
class BasicLayout extends Component { //编写组件,
//处理用户点击事件
clickHandle = () => {
console.log('hello')
}
render() {
return (
Sider
Header
Content
)
}
}
export default BasicLayout;
类组件必须提供render函数用来返回一个视图。
props是外界传递给组件的只读属性(不可修改),当我们要使用route、store时,使用注解实际上就是将route、store信息绑定到了props对象上。通过props的属性就可以拿到我们想要的。
import React from 'react'
import {observer,inject} from 'mobx-react'
import {withRouter} from 'react-router-dom'
@withRouter @inject('masterDatabaseStore') @form @observer //ES7的修饰器语法注解到组件上,
class MasterDatabase extends React.Component{
render(){
console.log('this.props',this.props)
return (
...
)
}
}
export default MasterDatabase
可以看到store和路由信息都已经绑定到props对象中去了。我们通过this.props.masterDatabaseStore可以拿到store状态。
props是不可改变的,react的状态定义到state中,通过调用this.setState()方法来改变状态,setState会自动调用组件的render方法来刷新视图。
当我们将页面拆分为不同的组件后,组件之间是相互隔离的,A组件中的状态不能直接在B组件中使用,反之亦然。React提供了状态提升来解决不同组件之间的通信问题,但是过于麻烦。我们使用mobx来管理状态。
mobx官网
为了方便代码维护,我们将页面细分成了更小的功能、UI组件,这样不仅代码易维护还能复用组件。拆分后的组件之间如何通信?
当页面需要拆分成不同的组件后,组件之前的状态传递就需要用到store来传递和共享
React 和 MobX 是一对强力组合。React 通过提供机制把应用状态转换为可渲染组件树并对其进行渲染。而MobX提供机制来存储和更新应用状态供 React 使用。
state状态只能在定义的组件中使用,而mobx的定义的store能在所有页面中使用,不仅解决了组件之间的通信问题,还将数据与视图完成分离:store中编写状态,react中定义视图。符合后端人员的思想
class TestStore {
@observable name //定义可观察的状态
constructor (){
this.name = 'lazyliang'
}
@computed get age(){
return this.name + '24'
}
@action //在action中改变状态,
changeName = ()=>{
this.name = 'hhhh'
}
}
...
@observer //监测状态
class Test extends React.Component{
render(){
return (
<div>
<div>name:{testStore.name}</div>
<button onClick={testStore.changeName}>改变name</button>
</div>
)
}
}
action
包装/装饰器只会对当前运行的函数作出反应,而不会对当前运行函数所调用的函数(不包含在当前函数之内)作出反应! 这意味着如果 action 中存在setTimeout
、promise 的then
或async
语句,并且在回调函数中某些状态改变了,那么这些回调函数也应该包装在action
中。创建异步 action 有几种方式。
当store状态在回调函数中改变或在异步函数后改变,则需要runInAction来改变状态
@action
changeName = ()=>{
setTimeout(()=>{ //在回调函数中改变状态要用runInAction
// this.name = "hhhh"
runInAction(() => {
this.name = "hhhh"
})
},1000)
}
@action
changeName = async ()=>{
await handle() //在异步函数后改变状态要用runInAction
// this.name = 'hhh'
runInAction(() => {
this.name = "hhhh"
})
}
react-router4简约教程
一个路由匹配一个组件
我目前所在公司的项目所需的技术栈为:
在node下使用webpack构建react开发环境,node是运行引擎,通过他可以直接在后端运行js语法,webpack是打包工具。
本项目使用 create-react-app 快速构建 React 开发环境,create-react-app 是来自于 Facebook,通过该命令我们无需配置就能快速构建 React 开发环境。
我们使用的webpack是基于nodejs的环境下开发的,可以看看自己的电脑中有没有安装node。打开命令窗口。输入node -v 就可以查看自己的node版本。
如果没有安装,请到nodejs中文网安装node。
一般而言,安装了node之后,npm自动集成在node环境中,你的电脑也会自动安装了npm。可以直接使用命令 npm -v 查看npm版本。
国内使用 npm 速度很慢,使用淘宝定制的 cnpm (gzip 压缩支持) 命令行工具代替默认的 npm 或者是直接 yarn
$ npm install -g cnpm --registry=https://registry.npm.taobao.org
cnpm install -g webpack //全局安装了webpack
//对这个命令的解读。
npm install 是下载命令。
-g是以全局方式下载
webpack是下载的内容。
webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler),作用:
① 模块化开发(import,require)
import与require的区别
② 预处理(Less,Sass,ES6,TypeScript……)
③ 对主流框架脚手架支持(Vue,React,Angular)
react 是一个基于 mvvm (Model-View-ViewModel)模式的组件化开发的 web 框架。
通过 React 构建组件,使得代码更加容易得到复用,能够很好的应用在大项目的开发中。
react-router:新的基本特性,看完获益颇丰。
区分React-router及React-router-dom
这里是 Ant Design 的 React 实现,开发和服务于企业级后台产品。
$ cnpm install antd --save
$ yarn add antd
├── components # 所有项目可复用的组件
│ ├── system # 系统基础模块
│ │ ├── Role
│ │ └── User
│ ├── LinkButton.js
│ └── SideMenu.js
├── framework # 所有项目可共用的工具类
│ ├── utils # 工具函数
│ │ ├── ajax.js
│ │ └── Section.js
│ ├── LoadableComponent.js # 组件懒加载,代码拆分函数
│ └── PrivateRoute.js # 路由验证函数
├── xxx # 项目
│ ├── components # 项目可复用的组件
│ ├── locales # 国际化
│ ├── routers
│ │ ├── backend # 后台模块
│ │ ├── frontend # 前台模块
│ │ ├── home # 首页模块
│ │ ├── backend.js # 后台路由
│ │ ├── frontend.js # 前台路由
│ │ └── home.js # 首页路由
│ ├── stores # 项目store
│ └── config.js # 项目配置
├── stores # 项目路由(根据env配置选择)
├── App.js # React根组件
└── index.js # 项目入口文件
在老项目中,路由没有分公司,所有项目都在一个文件中,比如后台路由都写在main.js中
导致的问题就是路由不清晰,维护困难
新项目中,项目的路由统一放在项目目录文件中。路由从main.js中抽到单个项目的backend.js文件里
新项目封装了一个组件来进行路由验证,所以现在路由和以前有点区别,比如后台路由写法:
//backend.js
import React from 'react'
import PrivateRoute from '../../framework/PrivateRoute'
import LoadableComponent from '../../framework/LoadableComponent'
export default [
<PrivateRoute
key="backend_user"
path="/backend/user"
component={LoadableComponent(import('./backend/system/User'))}/>,
<PrivateRoute
key="backend_role"
path="/backend/role"
component={LoadableComponent(import('./backend/system/Role'))}/>,
]
PrivateRoute是新封装的一个路由验证组件和Route类似,不过当未登录时会重定向到登录页面;path是页面路由;component是组件,LoadableComponent用于组件懒加载和代码拆分
和路由类似,在老项目中所有项目都放在src下的store文件中,在index.js中就会将所有store注入到react中。在新项目中可以看到目录结构发生了变化
├── xxx # 项目
│ ├── components # 项目可复用的组件
│ ├── locales # 国际化
│ ├── routers
│ │ ├── backend # 后台模块
│ │ ├── frontend # 前台模块
│ │ ├── home # 首页模块
│ │ ├── backend.js # 后台路由
│ │ ├── frontend.js # 前台路由
│ │ └── home.js # 首页路由
│ ├── stores # 项目store
│ └── config.js # 项目配置
路由和store都放在了项目目录中,
src下的store文件变成了:
import localeStore from './LocaleStore'
//根据env文件的配置动态引入项目store,老项目是将所有项目的store放在了下面
const stores = require(`../${process.env.REACT_APP_PROJECT_NAME}/stores`)
export default {
...stores.default
}
组件一律以大驼峰命名法(首字母大写)
文件名以小写命名