React最小系统的搭建

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


1、搭建开发环境

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

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

13908708-904322258073c142.png
npm install create-react-app -g



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

13908708-d0fe786f31b6c16d.png
13908708-406466a293d2ee0f.png
进入cmd中


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

13908708-0b96e143fc753b73.png
输入create-react-app demo后,回车执行
13908708-396138a68be5c64d.png

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

13908708-09fd0a4fe5b49516.png
13908708-581fb90c8f0d68f0.png
下载完成,项目构建成功  


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

13908708-23fe46e2156a550c.png
cd demo

启动项目

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

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


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

13908708-3a8eead627e539ff.png
node_modules\react-scripts\scripts\start.js


2、分析项目

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

13908708-cd4382c67982a63a.png
项目结构

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

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

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

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

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

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

》Readme.md, readme

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

13908708-bc0124c425ed1085.png
工程文件 脚本解析


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

13908708-c2ff501e0ffa7061.png
index.html

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


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

13908708-0706167d4679f2a9.png
index.js入口文件,相当与vue下的main.js文件


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

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

13908708-670c68f551e28915.png
实例化react应用

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



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

13908708-1e8c9851585c38e7.png
app组件

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

13908708-459bf0bd508f7c5a.png

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

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

13908708-d31b4d059a526a53.png

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

13908708-921b3ee388d873f3.png
npm run build  打包


3.项目组件划分

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

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

》Header组件(顶部)

》Home组件(首页)

》Acticlelist组件(文章列表)

》Acticledetail组件(文章详情)

》Footer组件(底部)


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

13908708-65d736fe7b0a599f.png
项目结构
13908708-4b680ffc52ca09a0.png
home组件
13908708-ac6b92ec71d37f6a.png
header头部组件
13908708-34b153553554631c.png
文章详情组件

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

13908708-02331080c358abb6.png
根组件app.js(index.js是入口文件,相当于vue的main.js,然后入口文件中用根组件app去渲染)
13908708-5443eb72acb682f0.png
渲染效果


3、加载react路由

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

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

13908708-2d910fa112e492dc.png
cnpm install react-router-dom --save


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

13908708-11128fc6387aed6d.png
工程文件自动更新

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

13908708-01259a6f304a20bb.png
设置路由模式并配置好路径与组件的关系


相关页面

13908708-0066a5952c9fab4c.png
http://localhost:3000/
13908708-37882e687f4ca7a8.png
http://localhost:3000/detail/2017122909




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

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

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

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

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

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

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

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


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

13908708-404d26680aeb7a56.png
测试钩子功能(生命周期)
13908708-eac3ac6cf05877c5.png
构造器执行时(初始化组件时)
13908708-15d7d8d3c0432f65.png
渲染完成时


接着分析一下代码

13908708-6faad52984650bdf.png
App.js   根组件

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


5、Redux状态管理

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

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

13908708-f37e7ab898851545.png
往header组件中传参

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

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

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

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

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

13908708-cfdf1e2cef28a2d3.png
log参数修改前
13908708-dd6b93a506cbb9ce.png
log参数修改后

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

13908708-68bd03b62d5a2e21.png
绑定上下文

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

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

13908708-feecd201e76d4d57.png
cnpm install redux react-redux --save


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

13908708-f2ba7c214f4547e0.png
创建reducers.js文件

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

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

13908708-be490756b39b37ce.png
根模块导入相关文件并定义全局状态管理器


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

13908708-b0015649d5a911fd.png
App.js 绑定仓库store


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

13908708-cba2abbeb73bb982.png
获取仓库快照


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


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

13908708-8899ae7e8a6848f8.png
设置仓库store中的值

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

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

13908708-0793699e32a3f19a.png
从仓库中出把值传输给组件
13908708-52f8d9450649f99c.png
控制台发现store的确被改变了,可是传递过去的值没有发现变化

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

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


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

13908708-ecbeb1a168afec6c.png
传递log参数进header组件


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

然后Header组件改动如下:

13908708-1f963926b8cec5ac.png
header组件企图修改store状态
13908708-163fd08c63703dbc.png
然而,报错了


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

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

13908708-3ab4cfc4db36523f.png
在header组件在connect连接仓库
13908708-d961973dad8b4ed4.png
解决了

完美没毛病


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

13908708-480a887ec9103790.png
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,代码如下:

13908708-de39397e0a6deac5.png
添加onchange事件

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


13908708-be9a59adc60335b7.png
渲染数据(双向数据绑定)


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

13908708-239df89ce506082f.png
添加点击事件

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

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

13908708-5104b8fa9dd166bf.png
header.js    通过refs获取dom节点信息


13908708-44371fcf8e51181b.png
触发点击事件


7、渲染服务端数据

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

13908708-49a973409be48ace.png
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、打包部署上线

13908708-24ac626cec52e3e3.png
打包
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值