一、React简介
官网: https://reactjs.org/
官方一句很简单的话,道出了什么是ReactJS,就是,一个用于构建用户界面的JavaScript框架,是Facebook开发的一款的JS框架。
ReactJS把复杂的页面,拆分成一个个的组件,将这些组件一个个的拼装起来,就会呈现多样的页面。ReactJS可以 用于 MVC 架构,也可以用于 MVVM 架构,或者别的架构。
ReactJS圈内的一些框架简介:
- Flux Flux是Facebook用户建立客户端Web应用的前端架构,
它通过利用一个单向的数据流补充了React的组合视图组件,这更是一种模式而非框架。 - Redux Redux 是 JavaScript 状态容器,提供可预测化的状态管理。Redux可以让React组件状态共享变得简单。
- Ant Design of React
Ant Design提供了丰富的组件,包括:按钮、表单、表格、布局、分页、树组件、日历等。
二、搭建环境
1、创建项目
使用UmiJS作为构建工具。
输入命令,进行初始化:
tyarn init -y
初始化完成后,在命令输入如下命令,导入umi依赖:
tyarn add umi --dev #项目中添加umi的依赖
.2、编写HelloWorld程序
第一步,在工程的根目录下创建config目录,在config目录下创建config.js文件
在UmiJS的约定中,config/config.js将作为UmiJS的全局配置文件。
在config.js文件中输入以下内容,以便后面使用:
//导出一个对象,暂时设置为空对象,后面再填充内容
export default {};
第二步,创建HelloWorld.js页面文件
在umi中,约定存放页面代码的文件夹是在src/pages,可以通过singular:false来设置单数的命名方式,我们采用 默认即可。
在HelloWorld.js文件中输入如下内容:
export default () => {
return <div>hello world</div>;
}
第三步,启动服务查看页面效果,在命令窗口输入下方命令:
#启动服务
umi dev
可以看到,通过/HelloWorld路径即可访问到刚刚写的HelloWorld.js文件。
在 umi 中,可以使用约定式的路由,在 pages 下面的 JS 文件都会按照文件名映射到一个路由,比如上面这个例子,访问 /helloworld 会对应到 HelloWorld.js。
3、添加umi-plugin-react插件
umi-plugin-react插件是umi官方基于react封装的插件,包含了13个常用的进阶功能。
具体可查看:https://umijs.org/zh/plugin/umi-plugin-react.html
#添加插件
tyarn add umi-plugin-react --dev
添加成功:
接下来,在config.js文件中引入该插件:
export default {
plugins: [
['umi-plugin-react', {
//暂时不启用任何功能
}]
]
};
4、构建和部署
通过umi可以进行转码生成文件的,具体操作如下:
umi build
至此,开发环境搭建完毕。
三、React快速入门
1、JSX语法
JSX语法就是,可以在js文件中插入html片段,是React自创的一种语法。JSX语法会被Babel等转码工具进行转码,得到正常的js代码再执行。
使用JSX语法,需要2点注意:
- 所有的html标签必须是闭合的。
- 在JSX语法中,只能有一个根标签,不能有多个。如:
const div1 = <div>hello world</div> //正确
const div2 = <div>hello</div> <div>world</div> //错误
在JSX语法中,如果想要在html标签中插入js脚本,需要通过{}插入js脚本。
2、组件
组件是React中最重要也是最核心的概念,一个网页,可以被拆分成一个个的组件。
2.1、修改Helloworld.js文件:
import React from 'react'; //第一步,导入React
class HelloWorld extends React.Component { //第二步,编写类并且继承 React.Component
render(){ //第三步,重写render()方法,用于渲染页面
return <div>hello world!</div> //JSX语法
}
}
export default HelloWorld; //第四步,导出该类
2.2、导入自定义组件
创建Show.js文件,用于测试导入组件:
import React from 'react'
import HelloWorld from './HelloWorld' //导入HelloWorld组件class
Show extends React.Component{
render(){
return <HelloWorld/>; //使用HelloWorld组件
}
}
export default Show;
测试:
2.3、组件参数
组件是可以传递参数的,有2种方式传递,分别是属性和标签包裹的内容传递,具体使用如下:
import React from 'react'
import HelloWorld from './HelloWorld' //导入HelloWorld组件class
Show extends React.Component
render(){
return <HelloWorld name="zhangsan">shanghai</HelloWorld>; //使用HelloWorld组件
}
}
export default Show;
其中,name="zhangsan"就是属性传递,shanghai就是标签包裹的内容传递。
对应接受的也是2种方法:
- 属性:this.props.name 接收;
- 标签内容:this.props.children 接收;
import React from 'react'; //第一步,导入React
class HelloWorld extends React.Component { //第二步,编写类并且继承 React.Component
render(){ //第三步,编写render()方法,用于渲染页面
return <div>hello world! name={this.props.name}, address={this.props.children}</div>
}
}
export default HelloWorld; //第四步,导出该类
测试:
2.4、组件的状态
每一个组件都有一个状态,其保存在this.state中,当状态值发生变化时,React框架会自动调用render()方法,重 新渲染页面。
其中,要注意两点:
一: this.state值的设置要在构造参数中完成;
二:要修改this.state的值,需要调用this.setState()完成,不能直接this.state进行修改;
下面通过一个案例进行演示,这个案例将实现:通过点击按钮,不断的更新this.state,从而反应到页面中。
import React from 'react'
class List extends React.Component{
constructor(props){ // 构造参数中必须要props参数
super(props); // 调用父类的构造方法
this.state = { // 初始化this.state
dataList : [1,2,3],
maxNum : 3
};
}
render(){
return (
<div>
<ul>
{
// 遍历值
this.state.dataList.map((value,index) => { return <li key={index}>{value}</li>
})
}
</ul>
<button
onClick={()=>{ //为按钮添加点击事件
let maxNum = this.state.maxNum + 1;
let list = [...this.state.dataList, maxNum]; this.setState({ //更新状态值
dataList : list, maxNum : maxNum
});
}}>
添加
</button>
</div>
);
}
}
export default List;
测试:
2.5、生命周期
组件的运行过程中,存在不同的阶段。React 为这些阶段提供了钩子方法,允许开发者自定义每个阶段自动执行的函数。这些方法统称为生命周期方法(lifecycle methods)。
生命周期示例:
import React from 'react'; //第一步,导入React
class LifeCycle extends React.Component {
constructor(props) {
super(props);//构造方法
console.log("constructor()");
}
componentDidMount() {
//组件挂载后调用
console.log("componentDidMount()");
}
componentWillUnmount() {
//在组件从 DOM 中移除之前立刻被调用。
console.log("componentWillUnmount()");
}
componentDidUpdate() {
//在组件完成更新后立即调用。在初始化时不会被调用。
console.log("componentDidUpdate()");
}
shouldComponentUpdate(nextProps, nextState){
// 每当this.props或this.state有变化,在render方法执行之前,就会调用这个方法。
// 该方法返回一个布尔值,表示是否应该继续执行render方法,即如果返回false,UI 就不会更新,默认返回true。
// 组件挂载时,render方法的第一次执行,不会调用这个方法。
console.log("shouldComponentUpdate()");
}
render() {
return (
<div>
<h1>React Life Cycle!</h1>
</div>
);
}
}
export default LifeCycle;
四、Model分层的概念
1、分层
上图中,左侧是服务端代码的层次结构,由 Controller、Service、Data Access 三层组成服务端系统:
- Controller 层负责与用户直接打交道,渲染页面、提供接口等,侧重于展示型逻辑。
- Service 层负责处理业务逻辑,供 Controller 层调用。
- Data Access 层顾名思义,负责与数据源对接,进行纯粹的数据读写,供 Service 层调用。
上图的右侧是前端代码的结构,同样需要进行必要的分层:
- Page 负责与用户直接打交道:渲染页面、接受用户的操作输入,侧重于展示型交互性逻辑。
- Model 负责处理业务逻辑,为 Page 做数据、状态的读写、变换、暂存等。
- Service 负责与 HTTP 接口对接,进行纯粹的数据读写。
2、使用DVA进行数据分层管理
dva是基于 redux、redux-saga 和 react-router 的轻量级前端框架。
官网:https://dvajs.com/
首先,我们先将dva框架引入进来,由于umi对dva进行了整合,所以导入就变得非常简单了。在config.js文件中进行配置:
export default {
plugins: [
['umi-plugin-react', {
dva: true // 开启dva
}]
]
};
接下来,创建model文件,在umi中,约定在src/models文件夹中定义model,所以,在该文件夹下创建ListData.js文件,编写内容:
export default {
namespace: 'list',
state: {
data: [1, 2, 3],
maxNum: 3
},
reducers : {
addNewData(state){ //state是更新前的对象
let maxNum = state.maxNum + 1;
let list = [...state.data, maxNum];
return { // 返回更新后的state对象
data : list,
maxNum : maxNum
}
}
}
}
下面对之前写好的List.js进行改造:
import React from 'react'
import { connect } from 'dva';
const namespace = "list";
//说明:第一个回调函数,作用:将page层和model层进行衔接,返回modle中的数据
//并且,将返回的数据,绑定到this.props
//接收第二个函数,这个函数的作用,将定义的函数绑定到this.props中,调用model层中定义的函数
@connect((state)=>{ //回调函数
return{
dataList :state[namespace].data,
maxNum: state[namespace].maxNum,
}
},(dispatch) =>{//dispatch的作用,可以调用model层定义的函数
return {//将放回的函数,绑定到this.props中
add : function () {
dispatch({//通过dispatch调用modle中定义的函数,通过type属性,指定函数命名,格式:namespace/函数名
type : namespace+"/addNewData"
})
}
}
})
class List extends React.Component{
render(){
return (
<div>
<ul>
{ this.props.dataList.map((value,index) => { return <li key={index}>{value}</li>}) }
</ul>
<button onClick={()=>{
this.props.add();
}}>添加</button>
</div>
);
}
}
export default List;
测试(可以看到效果是一样的):
流程说明:
- umi框架启动,会自动读取models目录下model文件,即ListData.js中的数据
- @connect修饰符的第一个参数,接收一个方法,该方法必须返回{},将接收到model数据
- 在全局的数据中,会有很多,所以需要通过namespace进行区分,所以通过state[namespace]进行获取数据
- 拿到model数据中的data,也就是[1, 2, 3]数据,进行包裹{}后返回
- 返回的数据,将被封装到this.props中,所以通过this.props.listData即可获取到model中的数据
点击按钮,需要修改state的值,流程梳理如下:
3、在model中请求数据
前面我的数据是写死在model中的,实际开发中,更多的是需要异步加载数据,那么在model中如何异步加载数据呢?
首先,创建src下创建util目录,并且创建request.js文件,输入如下内容:(用于异步请求数据)
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) { return response;
}
const error = new Error(response.statusText); error.response = response;
throw error;
}
export default async function request(url, options) { const response = await fetch(url, options); checkStatus(response);
return await response.json();
}
然后,在model中新增请求方法:
import request from '../util/request';
export default {
namespace: 'list',
state: {
data: [],
maxNum: 0
},
reducers : {
addNewData(state,result){ //state是更新前的对象,result:请求到的数据
if(result.data) {//如果state中存在data数据,直接返回,在做初始化的操作。
return result.data;
}
let maxNum = state.maxNum + 1;
let list = [...state.data, maxNum];
return { // 返回更新后的state对象
data : list,
maxNum : maxNum
}
}
},
effects: { //新增effects配置,用于异步加载数据
*initData(params, sagaEffects) { //定义异步方法
const {call, put} = sagaEffects; //获取到call、put方法
const url = "/ds/list"; // 定义请求的url
let data = yield call(request, url); //执行请求
yield put({ // 调用reducers中的方法
type : "addNewData", //指定方法名
data : data //传递ajax回来的数据
});
}
}
}
改造页面逻辑:
import React from 'react'
import { connect } from 'dva';
const namespace = "list";
//说明:第一个回调函数,作用:将page层和model层进行衔接,返回modle中的数据
//并且,将返回的数据,绑定到this.props
//接收第二个函数,这个函数的作用,将定义的函数绑定到this.props中,调用model层中定义的函数
@connect((state)=>{ //回调函数
return{
dataList :state[namespace].data,
maxNum: state[namespace].maxNum,
}
},(dispatch) =>{//dispatch的作用,可以调用model层定义的函数
return {//将放回的函数,绑定到this.props中
add : function () {
dispatch({//通过dispatch调用modle中定义的函数,通过type属性,指定函数命名,格式:namespace/函数名
type : namespace+"/addNewData"
})
},
initData : () => { //新增初始化方法的定义
dispatch({
type: namespace + "/initData"
});
}
}
})
class List extends React.Component{
componentDidMount(){
this.props.initData(); //组件加载完后进行初始化操作
}
render(){
return (
<div>
<ul>
{ this.props.dataList.map((value,index) => { return <li key={index}>{value}</li>}) }
</ul>
<button onClick={()=>{
this.props.add();
}}>添加</button>
</div>
);
}
}
export default List;
测试结果,发现会报错,原因是返回的数据不是json导致,解析出错。
查看下请求:
可以看到,返回的是html代码,所以会导致出错。
4、mock数据
umi中支持对请求的模拟,由于我们现在没有真正的服务可以返回数据,所以才需要模拟。在项目根目录下创建mock目录,然后创建MockListData.js文件,并且输入如下内容:
export default {
'get /ds/list': function (req, res) { //模拟请求返回数据
res.json({
data: [1, 2, 3, 4,5,6],
maxNum: 6
});
}
}
进行测试:
至此,ReactJS入们结束,开发时,首先要前后端约定好数据结构。