React最小系统的搭建

react

与Angular、Vue.js和微信小程序等开发一样,React也是一门数据驱动的语言(相对而言的dom驱动代表是jquery),其中Angular、Vue和React又称是新兴框架的三巨头。总的来说,React和Angular、Vue等的模式类似,一要学会其中一种,就可以快速入手其它任意一门。

1、搭建开发环境

开发环境基于nodejs,没有安装npm的需要先去了解前面的教程。

我们先全局安装create-react-app(react官网推荐的一个脚手架)

npm install create-react-app -g

安装完create-react-app后就可以搭建项目了,我们先找一个合适的位置,然后进入cmd中

进入cmd中

比如我们要创建一个叫demo的项目,输入create-react-app demo,回车后等待一段时间,项目会自动创建好

输入create-react-app demo后,回车执行

等待下载(大概132M),需要一定的时间,看网速了

下载完成,项目构建成功  

下载完成后cmd中进入到项目目录

cd demo

启动项目

直接启动项目

通过查看readme.md,我们知道启动项目的脚本是npm run start,运行成功后将跑在3000端口上(想要停止Ctrl + C 即可)

查看效果

默认情况下,运行成功后会自动在浏览器中打开(http://localhost:3000/),样式是默认的

如果发现端口被占用,可以先把其他3000端口停用,也可以把本项目的端口修改。修改本项目端口需要进入到(node_modules\react-scripts\scripts\start.js),然后搜索3000,修改掉就行(一般在第51行)

node_modules\react-scripts\scripts\start.js

2、分析项目

接着我们来分析这个项目是如何构成的

项目结构

》node_modules是npm模块,刚刚下载的132M模块大部分都在里面,其次我们人为安装的也会知道下载到里面(后面介绍安装路由模块和状态管理器)

》Public是公共资源,里面主要有首页、图标等(其中首页在开发模式下回自动导入相关的js)

》Src是和源码相关的,我们一般把开发的代码全部放在这里(将来会打包到build文件夹下)

》gitignore,看文件名就知道了,git的相关忽略配置,git会按照里面的配置自动忽略监听某些文件(默认就行,不用改,需要注意的是,该文件要在git初始化前创建,创建好修改不起作用,需要清除git缓存)

》Package-lock.json自动生成的文件

》Package.json项目配置文件(重要),我们项目的信息在这里有配置,例如版本和依赖等

》Readme.md, readme

》Build执行打包后生成的静态文件,可用于部署发布

工程文件 脚本解析

接下来我们先看public/index.html

index.html

 发现是一个极其简单的静态页面,没有导入任何的css和js,不过有点奇怪的是部分路径使用了%PUBLIC_URL%,查看注释发现编译的时候回自动把它替换成public文件夹的路径。同时,在开发环境下,会生成一个缓存文件,缓存文件是index.html的副本,但是会自动导入相关的js和css

再接着我们来看src/index.js,它是项目的项目的入口文件,开发是从这个文件正式开始的。

index.js入口文件,相当与vue下的main.js文件

Index.js入口文件中导入了react的核心文件,同时也导入了样式和一个叫App的组件,还有一个registerServiceWorker文件。为了排除没必要的影响,我们可以不导入registerServiceWorker,并把最后一行删除,其实这个文件是在开发环境下利用缓存加快加载速度的一个服务,删掉没有影响。

这样下来,整个index.js的核心代码就是

实例化react应用

意思是以index.html下id为root的节点为作用域,实例化react,接着把root的内容全部替换成App组件

既然说到index.js入口文件里面已经说到App组件,那就不得不说App组件了。App组件在src文件夹下,代码如下

app组件

通过代码可以看到App.js导入了react的核心代码和组件模块,然后定义了一个class继承Component组件(也就是定义了一个组件),最后把组件导出去供调用方使用。当然导出还可以改成下面的模式,是一样的:

每一个组件都有很多钩子(类似Vue下vue组件的钩子),有组件初始化钩子、组件开始构建时的钩子、组件构建完成时的钩子等等,其次最最核心渲染钩子,也就是上面看到的render函数。Render函数最终需要返回一个jsx对象,里面包含html和相关绑定的变量,写法和angular的模板、vue组件以及handlebars等极其相似,这里就没必要继续展开了(有些细节需要注意的是在es6下class是关键字,所以html里面的class要改成className)。

导出组件后,其他调用方就可以使用,比如index.js下是这样使用的:

如果代码开发完成了,脱离开发环境,代码是无法运行的,所以我们需要把代码打包,在cmd中输入npm run build即可,打包完成会项目目录下生成静态文件,上传到服务器部署即可

npm run build  打包

3.项目组件划分

当然这样是远远不够的,离一个可用的系统还差一段距离,接下来我们添加一个简单的功能,同时当页面开始多的时候我们也需要用上路由。先来说说要开发的项目是怎么样的

我们要开发的系统有首页、文章列表和文章详情,而且页面的顶部和底部要固定,每个页面要有一致的效果显示,所以我们可以分成下列的组件:

》Header组件(顶部)

》Home组件(首页)

》Acticlelist组件(文章列表)

》Acticledetail组件(文章详情)

》Footer组件(底部)

然后分别创建acticle、common和default文件夹,active文件夹放和文章相关的组件,common放通用的资源(如公共方法、路由配置和状态管理等),default放默认的组件(如home、header和footer)。

项目结构

home组件

header头部组件

文章详情组件

紧接着在根组件App中导入刚刚创建的全部组件,然后把里面的jsx代码替换成于它们相关的,代码如下:

根组件app.js(index.js是入口文件,相当于vue的main.js,然后入口文件中用根组件app去渲染)

渲染效果

3、加载react路由

组件初步开发好了,但不能切换,我们要做的是一个单页应用,为了加快开发进度,决定采用路由(react-router-dom)。

首先还是去下载模块(同时保持到项目配置文件中)

cnpm install react-router-dom --save

下载完成后发现package.json发生了变化

工程文件自动更新

接着我们去到根组件App里面导入路由模块,然后再进行相关配置(配置可问度娘)

设置路由模式并配置好路径与组件的关系

相关页面

http://localhost:3000/

http://localhost:3000/detail/2017122909

4、React组件钩子(钩子函数、也可以叫生命周期)

和angular、vue、小程序等一样,react组件有众多的钩子,配合钩子可以开发出复杂的应用。下面列举一些常用的钩子,不了解什么是钩子函数的可以看我之前的文章。

钩子                     执行时间                     作用                                     Vue下对应的钩子

constructor()    组件初始化时(又称构造函数)    初始化组件数据          Data()

componentWillMount()    即将渲染前组件  加载前的预处理      beforeCreate()、created()

Render()   正在渲染时                 返回jsx对象(html)                          类似template

componentDidMount()   渲染完成后           渲染后执行                               Mounted()

自定义钩子    手动调用                  处理具体业务逻辑                     Methods.自定义方法

下面以根组件为例,测试各个钩子

测试钩子功能(生命周期)

构造器执行时(初始化组件时)

渲染完成时

接着分析一下代码

App.js   根组件

在构造函数中,因为组件App要继承React的组件模块,所以在构造函数中要调用超类(父类)的构造函数,也就是圈出来的  super()  (如果涉及组件间通讯,构造函数还可以传入props参数,后面介绍)。在一个组件中,数据存在状态state中,每当状态state发送改变,UI也会发生相应的变化(双向数据绑定)。但是和angular和vue不一样,react直接改变 this.state 不会触发UI重新渲染,必须调用 this.setState(obj) 才会重新渲染数据,这和微信小程序下的setData一模一样,开发时稍微调整下思路即可。

5、Redux状态管理

在任何一个大型用于下,状态管理都是必不可少,状态管理器可以规范我们的数据,但是在开始接受状态管理前先介绍一下react下的组件间通讯

在上一个演示的基础上,我们修改根组件App如下

往header组件中传参

简单改动html部分(App.js)

接着我们需要在Header组件中接收刚刚的参数并把它渲染出来

header.js     执行超类构造器,接收传递过来的参数

修改继承并获取参数(Header.js)

在Header组件的构造函数中,必须传递进props参数,否则无法接收到父组件传递进来的参数,获取参数时使用 this.props.log 即可获得到,此时页面如下

log参数修改前

log参数修改后

题外话:如果存在需要子组件需要调用父组件方法的需求,写法也是差不多,父组件传递时传递方法名即可,但是有一点需要注意的,默认情况下,被调用的方法(假设是App下的debug方法)的上下文是props,取不到父组件的数据,如果需要去到父组件的数据,需要在父组件的构造函数中绑定上下文,如

绑定上下文

现在可以正式介绍状态管理了,但是需要注意状态管理不是必需的,不推荐滥用,只有在需要全局共享数据等情况下才建议使用,在不合适场景下使用反而会导致代码难以维护(比如可以用组件间通讯解决问题)。

我们使用redux状态管理器(在react下叫react-redux),我们先安装redux和react-redux

cnpm install redux react-redux --save

然后我们在common文件夹下创建一个reducers.js文件,它的作用是定义redux的全局状态,代码如下

创建reducers.js文件

代码中我们导入了redux模块,并且自定义了一个全局状态onlione(用来设置在线状态)模板,将来调用SET_ONLINE即可设置它的值(默认为false),调用获取快照可获取到其当前状态。

然后我们去根组件App中定义仓库存放状态,当然该导入还是得先导入,代码如下:

根模块导入相关文件并定义全局状态管理器

状态仓库定义好了,我们要去使用它,使用前得先设置仓库的使用范围,我们用Provider标签设置其范围,然后绑定上仓库store,代码如下:

App.js 绑定仓库store

仓库是绑定上去了,但是怎么获取仓库内的状态呢,我们叫获取快照,也就是获取当前的状态值,方法为 store.getState() ,我们测试一下

获取仓库快照

渲染完成后会输出仓库快照,online全局属性默认是false

问题又来了,怎么设置状态值呢,这就和我们以前开发的不太一样了,我们需要调用dispatch方法,如下

设置仓库store中的值

store.dispatch({type:"SET_ONLINE", payload: true});

状态的确改变了,使用状态里面的数据时直接绑定到state上即可,如

从仓库中出把值传输给组件

控制台发现store的确被改变了,可是传递过去的值没有发现变化

但是问题又来了,发现状态是更新了,但是UI没有发生变化,我们现在需要监听仓库里面状态的变化,然后实时setState,实现如下:

根组件 渲染完后监听store状态变化,动态修改变量,完成渲染

问题又来了,在子组件中如何获取到 online 或修改它呢(情况是希望在header组件中完成登录,然后其他组件能够获取到登录状态和用户信息)?获取和修改就需要用到我们前面说的组件间通讯了,我们把online参数传递进Header组件中,然后Header组件通过this.props.dispatch更新状态,此时App.js:

传递log参数进header组件

主要修改UI和屏蔽了更新状态的代码

然后Header组件改动如下:

header组件企图修改store状态

然而,报错了

很不幸,运行时发现报错, this.props 中并不存在 dispatch 属性

为什么会报错呢,因为我们还没把组件和redux仓库连接起来,我们要对Header组件再做一个小改动,连接一下仓库即可:

在header组件在connect连接仓库

解决了

完美没毛病

那么问题又来了,刚刚的操作其实是父子组件间的通讯,是在父组件(根组件)中监听store的改动,然后动态绑定到子组件的props上,在非父子组件中又如何获取到store里面的值呢,这个时候我们就需要订阅store了,也就是在组件连接上redux的时候,给它绑定上订阅事件,当store发送改变时,组件重新渲染。比如说我们要在路由Home中获取到online这个状态,我们需要先在Home组件中连接Redux,然后订阅store中online的更新,下面是具体实现

home.js 监听store中online的变化

然后Home组件中通过this.props.online即可拿到store中的值,同样,每当store的online发送变化时,Home组件会重新渲染online(这个过程就叫订阅),更多方法可以查看链接:http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_three_react-redux.html

PS:this.props一般是外部传递过来的,不可修改,而this.state是本组件的状态,可修改,要注意两者区别

现在总结一下react下redux的使用:

1.下载redux和react-redux

2.创建一个状态模板

3.根组件定义状态仓库

4.根组件添加仓库的作用域并绑定上去()

5.根组件通过store.subscribe监听状态变化,然后用store.getState获取状态快照,最后绑定到自己的state上

6.子组件需要更新全局状态,需要把组件和redux连接起来(connect,不需要更新可不连接),然后通过 this.props.dispatch 更新全局状态,需要获取状态从父组件获取即可

7.如果组价需要监听(订阅)store的更新,根组件使用store.subscribe,而其他组件可以使用订阅的方式来实现。

6、React交互事件

一个健壮的系统怎么可以没有交互呢,react下从很多交互事件,如click、change等,我们先在Header组件中添加一个组件,然后实现表单数据绑定,这时候开发就和angular和Vue不一样了,而于微信小程序更像,因为在表单中无法实现双向数据绑定,需要自己去监听表单change,代码如下:

添加onchange事件

开发过小程序就会发现这两者有异曲同工之妙(header.js)

渲染数据(双向数据绑定)

接着我们给它添加一个搜索按钮,并绑定点击事件(当然也需要绑定点击事件的上下文)

添加点击事件

要注意的是jsx里面的html不是真实的dom,如果需要获取dom元素,需要给标签添加refs属性,然后通过this.refs[...]即可获取到元素的dom元素

下面我们可以把上面的代码稍微修改一下

header.js    通过refs获取dom节点信息

触发点击事件

7、渲染服务端数据

除了事件交互,更重要的还有和服务器的交互,可以react并没有提供官方的ajax交互,我们可以使用jquery的ajax,也可以采用其他库,当然还可以选择h5下的fetch,下面是fetch的一个简要代码

fetch的使用

一般来说,不用fetch,而是采用axios插件,下面简单介绍一下axios的使用和封装http拦截器(vue、angular和小程序中也封装http拦截器,会方便很多)

import axios from 'axios';

import JavascriptCommon from './javascript.common';

import { Toast } from 'antd-mobile';

//基础设置

axios.defaults.timeout = 1000 * 60 * 2;

axios.defaults.baseURL = "你的http前缀";

axios.defaults.transformRequest = [

    function (data) {

        let ret = ""

        for (let it in data) {

            ret += encodeURIComponent(it) +

                "=" +

                encodeURIComponent(data[it]) +

                "&"

        }

        return ret

    }

];

//微信授权

axios.authorization = (appid, url, state) => {

    let _url = window.location.hash.toLocaleLowerCase();

    let flag = _url.indexOf("#/authorization/") === -1

        && _url.indexOf("#/recruit") === -1

        && _url.indexOf("#/valuation") === -1;

    if (flag) {

        window.location.href = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" +

            appid + "&redirect_uri=" +

            url + "&response_type=code&scope=snsapi_userinfo&state=" +

            state + "#wechat_redirect";

    }

};

// http请求拦截器

axios.interceptors.request.use(function (config) {

    config.headers = config.headers || {};

    //模拟登录需要设置sessionId,具体值参考登录后返回sessionid

    //JavascriptCommon.SetUserSessionId("**********");

    let sessionId = JavascriptCommon.GetUserSessionId();

    if (sessionId) {

        if (!config.headers["SESSIONID"]) { config.headers["SESSIONID"] = sessionId; }

    }

    JavascriptCommon.AjaxLoading(true);

    return config;

}, function (err) {

    return Promise.reject(err);

});

// http响应拦截器

axios.interceptors.response.use(function (res) {

    JavascriptCommon.AjaxLoading(false);

    try {

        if (typeof res.data === "object") {

            if (res.data.status) {

                if (res.data.status === -200) {

                    axios.authorization("appid", "编码后的回调地址", "weixin_h5");

                    return Promise.reject(res);

                } else if (res.data.status === -201) {

                    Toast.info(res.data.msg, 1.2);

                    window.location.hash = "/supplement";

                    return Promise.reject(res);

                }

            }

        }

    } catch (err) {

        console.log("请求异常", err);

    }

    return res;

}, function (err) {

    JavascriptCommon.AjaxLoading(false);

    return Promise.reject(err);

});

export default axios;//最后导出模块

使用方式如下:

import axios from './common/axiosConfig'; //导入封装的模块

//发起请求

axios.get("/interest/category").then((res) => {

      store.dispatch({ type: "SET_CATEGORY", payload: res.data });

});

8、打包部署上线

打包

以上是 React最小系统的搭建 的全部内容, 来源链接: utcz.com/z/384424.html

回到顶部