React篇3--ajax、路由、UI组件库、redux
一、react应用(基于react脚手架)
脚手架:用来帮助程序员快速创建一个基于xxx库的模板项目。
(1)包含了所有需要的配置(语法检查、jsx编译、devServer)
(2)下载好了所有相关的依赖
(3)可以直接运行一个简单的效果
react提供了一个用于创建react项目的脚手架库:create-react-app。
项目整体技术架构为react+webpack+es6+eslint。
使用脚手架开发的项目的特点:模块化、组件化、工程化。
react脚手架有webpack。
创建的模板项目内容:

上图中提示了:修改App.js文件并保存后页面会自动重新加载。
注意:使用react写项目时只有一个index.html文件(一个页面),其他都为组件。

说明
(1)可以看到,index.js将APP组件绑定到index.html中的id="root"的div节点。
(2)<React.StrictMode></React.StrictMode>用来检查APP中有问题的地方。
注意:.js文件和jsx文件在import时可以省去后缀(.js和.jsx),其他文件后缀不能省略


组件的组合使用-TodoList案例
模块化和初始化



先来思考可以把页面拆分成几个组件。可以是输入框一个组件Header,下面的list一个组件List,list中的每一项是一个组件Item,最后的一行(全选、全不选)一个组件Footer。


有一个问题,Header组件的输入需要传递给List的组件,但是组件之间传递数据还没有讲述,只讲述了父组件向子组件传递数据,所以存储数据的state需要放到父组件App中(称为“状态提升”)。


实现添加
要实现输入框的内容传递到App的state的数据中,需要Header子组件给 App父组件传递数据,方法是父组件通过props给子组件传递一个函数,在函数中对数据进行处理。


父组件可以获取到子组件的数据,接下来实现添加功能。
yarn add nanoid #下载生成唯一id(uuid)的库

可以实现输入后添加效果。
鼠标移入




对props进行限制
接下来进行App组件对Header和List组件传递props进行类型及必要性的限制。
下载依赖:
yarn add prop-types


删除功能
也是父组件App传递给子组件List一个删除方法。


全选功能


使用数组的reduce()方法对数据进行条件统计。reduce方法是对数组进行遍历,第一个参数是一个方法,方法的第一个参数pre是上一次遍历的返回值(第一次遍历是reduce的第二个参数0),方法第二个参数todo是当前遍历数组元素,每次遍历改变返回值。这个reduce方法是计算出数组中元素的done值为true的个数。
接下来实现全选后的底部按钮也被自动勾选的功能:


但是会报错,因为checked属性被赋值,onChange也需要被赋值。
上图中,全选按钮选择后需要改变List中的值,所以handleCheckAll()方法还是需要从父组件App传递过来。

但是还有一个问题,

另外,加上全取消功能:

总结

二、react ajax
React本身只关注界面,并不包含发送ajax请求的代码。
前端应用需要通过ajax请求与后台进行交互(json数据)。
react应用中需要集成第三方ajax库或自己封装。
常用的ajax请求库
1.jQuery:比较重,如果需要另外引入不建议使用。
2.axios: 轻量级,建议使用。
(1)axios封装XmlHttpRequest对象的ajax
(2)axios采用promise风格
(3)可以用在浏览器端和node服务器端
下载axios库:
yarn add axios
代理(解决跨域问题)
先来看一个axios发送get请求的案例:

修改react项目中的请求地址:

注意:上述跨域请求还是达到了后台服务,但是请求的数据回不来。
解决跨域需要通过代理解决。这个代理是一个中介,该服务的端口号也是3000,它可以给5000发请求并且可以返回数据(因为代理发送的不是ajax请求,不会产生跨域问题)。
配置代理方法1
接下来在react项目中配置代理,在package.json文件中配置。

配置代理方法2
配置代理方法1中的方式有点问题,只能配置一个代理,即端口号3000下的服务没有的内容都找5000请求。
需要请求一个新的端口号服务的数据:
配置多个代理需要在src目录下新建一个setupProxy.js文件:
这个文件不能用ES6语法写,需要用CJS(即common javascript)语法写。react会找到这个文件,加到webpack的配置中,webpack写的都是CJS(内容不用记,知道要配就行)。
说明:





github搜索案例

输入内容后返回搜索到的结果数据:



静态数据效果:
注意:List组件中的imag节点中的href值需要是有效的图片地址(可以去百度找一些图片把图片地址粘贴进来)。
补充:JS的解构赋值
解构赋值的一般写法如下:

解构赋值的连续写法:

解构赋值重命名:

继续案例。



上述不会产生跨域问题是因为github服务器后端用cors直接解决了跨域,请求它的服务不会产生跨域。但是github服务请求多次会不响应,需要用其他服务。





返回中的avatar_url是头像地址,html_url是个人主页地址。
List获取到数据,需要传给search展示,所以数据存放在他们共同的父组件App的state中。
App传递存储返回users数据的方法给Search。
Search将返回结果调用App的方法存储到App的state中。

给List中的多个元素加key属性,id是请求返回的数据里的字段。
消息订阅与发布pubsub(兄弟组件之间通信)
实现兄弟组件之间通信的消息订阅和发布需要使用依赖库pubsub。
yarn add pubsub
原理是组件A发布消息并携带数据,组件B若订阅了组件A的消息,则在组件B可以获取到组件A发布的消息和数据。
发布消息:
import PubSub from 'pubsub.js'
PubSub.publish('MY_TOPIC',"hello,world!")
订阅消息:
import PubSub from 'pubsub.js'
var mySubscriber = function(msg,data){
console.log(msg,data);
};
var token = PubSub.subscribe('MY_TOPIC',mySubscriber)

对于之前的例子,Search组件需要把数据传给List组件,所以是Search发布消息,List订阅消息。



继续实现github搜索功能:


注意:其实消息时间订阅与发布不仅适用于兄弟组件之间,任何组件之间都可以。
fetch发送请求
JS发送ajax请求方式有:
(1)xhr:
const xhr = new XmlHttpRequest();
(2)jQuery:底层是对XmlHttpRequest的封装
(3)axios:底层是对XmlHttpRequest的封装
还有一个内置库,可以不用xhr发送ajax请求,是fetch。

注意:这里输出的内容没有返回数据,这是一个粗略的建立和服务器建立连接的请求。


注意,不是请求两次,是请求后先进行请求是否成功的判断,再分别进行处理,成功则获取其他数据,失败报错。
可以对上述写法进行优化:
用await也可以实现上述功能:
await只等待成功的结果,失败的结果不管,所以要加上try-catch处理异常情况。
使用fetch请求实现消息发布和订阅:
注意:fetch是原生,使用率不高,因为老版本浏览器不兼容(了解即可)。
三、React路由
3.1 SPA
常常需要实现如下效果:

以前是使用多个页面的形式,如上面的About栏和Home栏对应的展示区内容是about.html和home.html两个页面。
SPA的模式(SPA即single page web application,单页web应用):
整个应用只有一个页面;点击页面链接不会刷新页面,只做局部更新即改变展示区的组件(使用路由技术);数据都需要通过ajax请求获取,并在前端异步展现。
3.2 路由
路由定义
一个路由就是一个映射关系(key:value),key是路径,value可能是function或component。每一个路径(导航栏中每一项)对应一个组件。
路由分类
路由分为前端路由和后端路由。
后端路由示例(node.js编写):

前端路由原理
前端路由需要靠浏览器的history。
浏览器的history
前端程序员通过BOM操作history。
history.push()是往浏览器的历史记录中存入一条数据。





a标签绑定onclick方法再进行测试:




浏览器的hostory记录是一个栈的结构。
点击后退按钮就是把栈顶元素出栈跳转到对应页面。
在上图中在点击浏览器的回退按钮或者点击回退按钮,直接从test3.html回退到test1.html。
history两种工作模式
方式1:直接使用H5推出的history的API
let history = History.createBrowserHistory()
方式2:hash值(锚点)
let history = History.createHashHistory()

点击push test2按钮跳转:
看一个例子:





3.3 路由的基本使用react-router
react-router是react的一个插件,专门用来实现一个SPA应用,基于react的项目基本都会用到此库。专门用于we应用的库是react-router-dom库。
yarn add react-router-dom@5
注:因为react-router-dom更新为版本6,下面的讲述是基于版本5的。



在React中靠路由链接Link组件实现切换组件,即导航区使用Link组件,Link组件需要写在BrowserRoute组件内部。

Link组件中的属性to的值就是点击该组件的跳转地址。
先来看效果:

接下来实现展示区内容:


上面的代码能实现功能,但是如果要加内容的话需要把BrowserRouter标签继续往外包。有一个方便的方法,直接把BrowserRouter包在组件App外面:



总结:
之前说过,有两种路由器BrowserRouter和HashRouter,将App组件使用HashRouter试试:

路由组件VS一般组件
可以看到,在使用路由组件后没有使用:
<Home/>
<About/>
上面的组件是一般组件。
标准化开发一般把路由组件放在pages目录下。
之前讲述过,一般组件通过props传递数据,没传props是空。
Router组件会默认传递一些数据。
点击导航栏的About:
NavLink
要给导航栏选中的模块加高亮效果,需要给标签加active。
但是不能写死。为了方便,可以使用NavLink标签,它自动实现高亮效果。

效果:
也可以通过NavLink的属性activeClassName(默认值为active)自定义样式:


封装NavLink




上图中标签内的内容通过title属性传递。
另外,想进一步优化成如下写法(通过标签体传递内容):
<MyNavLink>Home</MyNavLink>
<MyNavLink>About</MyNavLink>
其实,标签体也是一个标签属性children:

所以,封装组件可以优化为:
其实,上图中的代码等效于下图代码
3.4 switch使用
现在的效果是,刚进入页面时展示区默认为空:
只有点击左侧导航栏后才会展示对应内容。
React中若Router组件有多个,路由改变后会遍历所有的Router组件把符合的都展示在展示区,即Router组件的to值可以相同,且一起展示。但这样效率较低,可以使用switch设置找到一个符合条件的Router组件后就不再匹配。

3.5 解决样式丢失问题


但是存在一个问题,刷新页面后样式会丢失:
先来说一些其他的东西。
项目的public目录时项目的根路径。即http://localhost:3000等同于public目录。
若是访问的资源在public目录里找不到,则默认返回public中的index.html文件。
从上图中可以看出bootstrap.css文件的请求地址。
使用/atguigu/home和/atguigu/about两级目录:
启动服务首次访问bootstrap.css的请求路径正常:


找不到也是200,但是返回内容为index.html。
原因是当有两级目录时,把http://localhost:3000/atguigu当成了public目录。
1.删除./

2.加%PUBLIC_RUL%

3.



3.6 路由的模糊匹配和严格匹配
模糊匹配


可以看到,NavLink的to属性前缀与Route的path属性相匹配就是匹配成功。
但注意下面的情况:

默认是模糊匹配。
精准匹配


注意:开启严格匹配可能会引发严重的问题,默认不开启。
3.7 Redirect使用

用户输入http://localhost:3000访问时,Route组件中没有匹配的,这时可以加一个Redirect组件,找不到匹配的Route组件则按照Redirect设置的路由规则自动跳转页面。
3.8 嵌套路由(二级路由)

将Home组件差分,拆分为News组件和Message组件。


加上外层路由路径后外层路由能够模糊匹配。

这里解释下,当点击了News导航栏后,于是路径变成了/home/news,路由的匹配按照注册顺序,先是对App组件内的路由匹配,匹配到/home,Home组件展示。再对Home组件内的路由进行匹配,匹配到/home/news,News组件展示。
所以不能给一级路由开启严格匹配exact,因为有二级路由的情况下,开启了严格匹配的一级路由的所有子路由都失效了。
3.9 向路由组件传递参数数据
用一个案例说明。
目标效果:

可以看出,Message又出现一个导航区和展示区,上面的Message001、Message002、Message003的区域是导航区,下面的文字是展示区。






现在遇到一个问题,需要把消息1(Link)的内容传递给下面的文本框Detail,这就是路由传递携带参数的情况
路由组件传递参数的方式有三种。
params参数
先实现一种简单的模式。

注意:上面Link组件的的to属性不能用字符串的形式(因为要传变量名),需要`符号(模板字符串,ES6+,可以嵌入变量),再用花括号,表示JS中的
这样的写法传递的参数默认在Detail组件中的props中。



总结:
search参数



可以看到search参数没有对参数整理成json对象的形式,可以用一个react脚手架自动下载的库qs实现转换,使用示例如下:


state参数
注意:这个state参数是路由组件特有的,不要和组件特定state弄混。
state参数的优点是是路由路径和参数分开写。




注意:使用state参数传递url中没有携带参数数据,刷新页面也能正常显示,因为BrowserRouter操作浏览器的history,state数据在history中的location中。
但是如果删除了缓存再刷新就会有问题,解决方法是对空数据的情况进行处理:


params参数使用较多,其次是search,再其次是state。一般要是传递的参数是私密信息(比如手机号)就要使用state参数。
3.10 push和replace
之前说过路由就是多浏览器的history进行操作(包括push和replace),默认的路由操作是push,也就是每一次进行路由跳转都会留下痕迹。

刚进入页面默认是localhost:3000/about(重定向),点击Home后localhost:3000/home/news入栈,再点击Message后localhost:3000/home/message入栈,点击“消息1”后localhost:3000/home/message.detail入栈。
点击后退会依次倒退。
那如何开启replace模式呢?
用一个示例说明,点击“消息1”、“消息2”、“消息3”使用replace模式。

3.11 编程式路由导航
之前使用的都是Link组件进行路由导航。但是若是要实现一种功能,News组件等待3s后往Meassage组件跳转。
编程式路由导航就是切换路由可以通过代码设定,不一定通过点击触发。
用一个例子说明:

push 按钮的实现方法类似:

可以看出这个方法携带的是params参数。


state参数形式:


另外,还可以使用goBack(),goForward方法实现回退、前进功能。


history还有一个方法是go(),go(1)表示前进一步,go(2)表示前进两步;go(-1)表示后退一步,go(-2)表示后退两步。

3.12 withRouter的使用



报错的原因是Header的使用是一般组件,没有this.props.history。
想让一般组件也使用路由组件的history,需要使用插件withRouter。
withRouter可以把一般组件加上路由组件的API。
3.13 BrowserRouter和HashRouter区别

四、React UI组件库
接下来学习一些UI组件的使用,帮忙绘制页面。
antd
antd是ant-design(国内蚂蚁金服)创作的插件。



安装antd组件:
yarn add antd







还有其他的UI 组件库,比如Elment-UI、vant-UI(针对移动端设计给vue用)。
五、redux
redux是一个专门用于做状态管理的JS库(不是react插件库),它可以用在react、angular、vue等项目中,但基本与react配合使用,作用是集中式管理react应用中多个组件共享的状态。
现在有一个需求是组件D的数据{a:1,b:2},组件A、B、C、E、F都要使用,之前说过可以把数据给App的state,通过赋值箭头方法给组件属性的方式,还有消息订阅与发布的形式,因为通信较多这样写起来很麻烦,若是使用redux就会变得简单。
redux是独立所有组件存在的,D将数据交给redux,其他组件去redux中获取数据。

5.1 redux原理图和3个核心概念

通过一个案例来说明redux原理图,例子要实现的效果为,左上角的数字为当前值,下拉框选择一个数据,右边再选择一个运算符,计算后左上角数据变为(当前值)(运算符)(下拉框选择的数字),例如,左上角当前值为0,下拉框选择1,选择+,则左上角数字变为1(0+1=1).

ReactComponents为组件,对于0+1=1操作,组件把+1的操作告诉ActionCreators,ActionCreator把动作对象action(包括type和data)分发给Store(相当于调度者,负责全局的掌控,不存储加工数据,只分发),Store把数据(previousState旧数据、action动作,如上例中previousState=0,action=+1)交给Reducers处理完后返回newState给Store,ReactComponents通过getState()从Store获取newState。另外,Reducers也可以初始化状态,传递的previousState为undefined,action包含初始化动作类型和数据。
注意:上图中store只有一个,其他元素可以有多个。
对于初学者,Action Creator可以不用,自己创建。
5.2 redux案例
纯react版









简化版redux
先只有Store和Reducers,不用ActionCreators.
安装redux
yarn add redux





组件使用store和reducer:


可以看到还没有调用reducer就初始化了,即执行了countReducer(),是store自行调用的。
注意:redux中reducer数据变化不会自动引起页面数据的更新。需要再调用render()。


代码简化:


注意:redux只负责管理状态,至于状态的改变驱动着页面的展示,需要程序员自己写。
完整版redux
完整版相对与简化版多了ActionCreator,会新增两个文件。
一个文件是ActionCreator相关。


在实际开发中,ActionCreator的type的枚举值不会直接写在ActionCreator中,新建一个常量模块管理。


异步action
方法返回值为对象Object的action是同步action,返回值为函数的action是异步action。
之前异步加的实现:
可以在组件里写等待执行,直接使用redux的异步action。

但是上面的代码会报错,因为store不允许actioncreator返回函数。
解决该问题需要先下载一个中间件:
yarn add redux-thunk


还有一个需要完善的地方。
注意:异步action中一般会调用同步action。
5.3 reactc-redux
注意:之前redux是另一个团队做的,后来facebook公司发现在react中使用redux的人很多,就自己做了一个react-redux插件。
UI组件一般写在component目录,容器组件写在container目录。
下载:
yarn add react-redux



原因是找不到store。


我们可以看到Count容器组件和UI组件Count的父子关系不是显示写的(在connect()())中,不能通过属性赋值(props)把数据传给子组件。
可以在connect()()第一个()传递两个参数(参数需要是方法),第一个函数参数的返回值作为状态传递给UI组件,第二个函数参数的返回值是操作状态的方法(上面react-redux模型图的第4点)


容器组件Count的store是从App组件传递过来的:


接下来实现计算功能:





继续实现减法和奇数加法:

继续实现异步加方法:

简写mapDispatch


虽然简写方式没有添加dispatch进行分发,但是react-redux会执行分发。
provider组件的使用
另外,connect()()中具有了监测redux中状态改变的能力,可以不用自己加监测。

使用Provider给App组件中所有的容器组件加上store。
整合UI组件和容器组件

多个Reducer





redux的reducer函数是纯函数


更多推荐

所有评论(0)