ReactJS入门

1. 前端开发的演变

到目前为止,前端的开发经历了四个阶段,目前处于第四个阶段。这四个阶段分别是:

阶段一:静态页面阶段

在第一个阶段中前端页面都是静态的,所有前端数据都是后端生成的。前端只是纯粹的展示功能,js脚本的作用只是增加一些特殊效果,比如那时很流行用脚本控制页面上飞来飞去的广告。那时的网站开发,采用的是后端 MVC 模式,前端只是后端 MVC 的 V。
Model (模型层):提供/保存数据
Controller (控制层):数据处理,实现业务逻辑
View (视图层):展示数据,提供用户界面

阶段二:ajax阶段

2004年,AJAX 技术诞生,改变了前端开发。Gmail 和 Google 地图这样革命性的产品出现,使得开发者发现,前端的作用不仅仅是展示页面,还可以管理数据并与用户互动。就是从这个阶段开始,前端脚本开始变得复杂,不再仅仅是一些玩具性的功能。

阶段三:前端MVC阶段

2010年,第一个前端 MVC 框架 Backbone.js 诞生。它基本上是把 MVC 模式搬到了前端,但是只有 M (读写数据)和 V(展示数据),没有 C(处理数据)。有些框架提出了 MVVM模式,用 View Model 代替 Controller。Model 拿到数据以后,View Model 将数据处理成视图层(View)需要的格式,在视图层展示出来。

阶段四:SPA阶段

前端可以做到读写数据、切换视图、用户交互,这意味着,网页其实是一个应用程序,而不是信息的纯展示。这种单张网页的应用程序称为 SPA(single-page-application)。2010年后,前端工程师从开发页面(切模板),逐渐变成了开发“前端应用”(跑在浏览器里面的应用程序)。目前,最流行的前端框架 Vue、Angular、React 等等,都属于 SPA 开发框架。

2. ReactJS简介

官网: 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
阿里开源的基于 React的企业级后台产品,其中集成了多种框架,包含了上面提到的Flux、Redux。
Ant Design 提供了丰富的组件,包括:按钮、表单、表格、布局、分页、树组件、日历等。

3. 搭建环境

3.1 创建项目

使用IDEA或其他Web开发工具,创建一个普通工程,在工程目录进入命令行输入命令,进行初始化:tyarn init -y
初始化完成会在项目路径下生成package.json文件,然后添加umi依赖。
tyarn add umi --dev #项目中添加umi的依赖

这个过程比较耗时,添加完依赖会在工程路径下生成node_models目录及yarn.lock文件。

3.2 编写HelloWorld程序

在UmiJS的约定中,config/config.js将作为UmiJS的全局配置文件。在工程的根目录下创建config目录,在config目录下创建config.js文件。
在这里插入图片描述

在 umi中,约定的目录结构如下:
在这里插入图片描述

在config.js文件中输入以下内容,以便后面使用:

// 导出一个对象,暂时设置为空对象,后面再填充内容
export default {};

在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.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', {
      //暂时不启用任何功能
   }]
 ]
};
3.4 构建和部署

现在我们写的js,必须通过umi先转码后才能正常的执行,那么我们最终要发布的项目是普通的html、js、css,那么应该怎么操作呢?其实,通过umi是可以进行转码生成文件的,具体操作如下:umi build
在这里插入图片描述
构建成功之后,会生成 index.html和umi.js文件,我们的代码被压缩到umi.js文件中。

4 React快速入门

4.1 JSX语法

JSX语法就是,可以在js文件中插入html片段,是React自创的一种语法。JSX语法会被Babel等转码工具进行转码,得到正常的js代码再执行。使用JSX语法,需要2点注意:

  1. 所有的html标签必须是闭合的;
  2. 在JSX语法中,只能有一个根标签,不能有多个。
const div1 = <div>hello world</div> //正确
const div2 = <div>hello</div> <div>world</div> //错误

在JSX语法中,如果想要在html标签中插入js脚本,需要通过{}插入js脚本。

4.2 组件

组件是React中最重要也是最核心的概念,一个网页,可以被拆分成一个个的组件。

定义组件

import React from 'react'; //第一步,导入React
class HelloWorld extends React.Component { //第二步,编写类并且继承 React.Component
  render(){ //第三步,重写render()方法,用于渲染页面
    return <div>hello world!</div> //JSX语法
 }
}
export default HelloWorld; //第四步,导出该类

导入自定义组件

import React from 'react'
import HelloWorld from './HelloWorld' //导入HelloWorld组件
class Show extends React.Component{
  render(){
    return <HelloWorld/>; //使用HelloWorld组件
 }
}
export default Show;

组件参数
组件是可以传递参数的,有 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就是标签包裹的内容传递。那么,在HelloWord.js组件中如何接收参数呢?对应的也是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> //JSX语法
 }
}
export default HelloWorld; //第四步,导出该类

组件的状态
每一个组件都有一个状态,其保存在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;

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

生命周期
组件的运行过程中,存在不同的阶段。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;

5 Model

5.1 分层

在这里插入图片描述
上图中,左侧是服务端代码的层次结构,由 Controller、Service、Data Access 三层组成服务端系统:

  • Controller 层负责与用户直接打交道,渲染页面、提供接口等,侧重于展示型逻辑。
  • Service 层负责处理业务逻辑,供 Controller 层调用。
  • Data Access 层顾名思义,负责与数据源对接,进行纯粹的数据读写,供 Service 层调用。

上图的右侧是前端代码的结构,同样需要进行必要的分层:

  • Page 负责与用户直接打交道:渲染页面、接受用户的操作输入,侧重于展示型交互性逻辑。
  • Model 负责处理业务逻辑,为 Page 做数据、状态的读写、变换、暂存等。
  • Service 负责与 HTTP 接口对接,进行纯粹的数据读写。
5.2 使用DVA进行数据分层管理

dva是基于 redux、redux-saga 和 react-router 的轻量级前端框架。官网
在这里插入图片描述
在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';
const mapStateToProps = (state) => {
    const listData = state[namespace].data;
    const maxNum = state[namespace].maxNum;
    return {
        listData, maxNum
    };
};
const mapDispatchToProps = (dispatch) => { // 定义方法,dispatch是内置函数
    return { //返回的这个对象将绑定到this.props对象中
        addNewData : () =>{ // 定义方法
            dispatch({ // 通过调用dispatch()方法,调用model中reducers的方法
                type: namespace + "/addNewData" // 指定方法,格式:
namespace/方法名
            });
        }
    }
}
@connect(mapStateToProps, mapDispatchToProps) //mapDispatchToProps:函数,
将方法映射到props中
class List extends React.Component{
    render(){
        return (
            <div>
                <ul>
                    {
                        // 遍历值
                        this.props.listData.map((value,index) => {
                            return <li key={index}>{value}</li>
                        })
                    }
                </ul>
                <button    onClick={()=>{this.props.addNewData()}}>
                    添加
                </button>
            </div>
        );
    }
}
export default List;

可以看到,效果是一样的。
流程说明:

  1. umi框架启动,会自动读取models目录下model文件,即ListData.js中的数据
  2. @connect修饰符的第一个参数,接收一个方法,该方法必须返回{},将接收到model数据
  3. 在全局的数据中,会有很多,所以需要通过namespace进行区分,所以通过state[namespace]进行获取数据
  4. 拿到model数据中的data,也就是[1, 2, 3]数据,进行包裹{}后返回
  5. 返回的数据,将被封装到this.props中,所以通过this.props.listData即可获取到model中的数据
5.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;
}

/**
 * Requests a URL, returning a promise.
 *
 * @param  {string} url       The URL we want to request
 * @param  {object} [options] The options we want to pass to "fetch"
 * @return {object}           An object containing either "data" or "err"
 */
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: 1
    },
    reducers : { // 定义的一些函数
        addNewData : function (state, result) { // state:指的是更新之前的状态数据, result: 请求到的数据

            if(result.data){ //如果state中存在data数据,直接返回,在做初始化的操作
                return result.data;
            }

            let maxNum = state.maxNum + 1;
            let newArr = [...state.data, maxNum];


            return {
                data : newArr,
                maxNum : maxNum
            }
            //通过return 返回更新后的数据
        }
    },
    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"
            });
        },
        init : () => {
            dispatch({ //通过dispatch调用modle中定义的函数,通过type属性,指定函数命名,格式:namespace/函数名
                type : namespace + "/initData"
            });
        }
    }
})
class  List extends React.Component{

    componentDidMount(){
        //初始化的操作
        this.props.init();
    }

    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;
5.4 mock数据

umi中支持对请求的模拟,由于我们现在没有真正的服务可以返回数据,所以才需要模拟。在项目根目录下创建mock目录,然后创建MockListData.js文件,并且输入如下内容:

export default {
    'get /ds/list': function (req, res) { //模拟请求返回数据
        res.json({
            data: [1, 2, 3, 4],
            maxNum: 4
        });
    }
}

在这里插入图片描述

发布了105 篇原创文章 · 获赞 7 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_43792385/article/details/99986442
今日推荐