react(二)

React路由

前端路由原理

  1. BOM上有一个history属性 专门用于管理浏览器额路径、历史记录等 我们可以直接操作 但是写起来麻烦 所以我们借用一个库来操作
  2. 路由器router用于管理路由route,整个应用只能用一个路由器router来管理所有路由
  3. 浏览器的历史记录是一个栈的结构 你看到的页面就是栈顶的页面 你点击一个链接 就是往栈中push一个URL push的话可以后退 就是弹出当前页面 你点击一个链接 也可能是replace栈顶的URL 此时后退返回的是上上一个页面 因为上一个页面已经被取代了
  4. history.push可以往栈中推入一个URL
  5. history.listen可以监听路径的变化
  6. history.back后退
  7. history.forward前进
  8. 前端路由靠的就是BOM身上的history
  9. 锚点跳转不会刷新页面但是会留下历史记录
  10. history有两种工作模式:
    (1)let history = History = History.createBrowserHistory()// 方法一 直接使用H5推出的history身上的API
    (2)let history = History.createHashHistory()// 方法二 hash值(锚点)

react-router-dom的理解

  1. 安装:npm i react-router-dom --save
  2. 使用路由的套路:首先找到导航区和展示区,点击导航区的不同导航选项(即路由链接),会在展示区展示对应的内容,前端路由的原理:点击导航区的导航,引起历史记录中路径的变化,被前端路由器监视到,发现路径是/home,就把相应的组件展示出来。总的来说也就两步:点击导航链接,引起路径改变;路径的变化被路由器检测到,进行匹配组件,从而展示相应的组件
  3. App.jsx中的代码:
import React, { Component } from 'react'
import {Link,Route} from 'react-router-dom'
import Home from './components/Home'
import About from './components/About'
// 项目一启动 展示的就是APP中的内容
export default class App extends Component {
	render() {
		return (
			<div>
				<div className="row">
					<div className="col-xs-offset-2 col-xs-8">
						<div className="page-header"><h2>React Router Demo</h2></div>
					</div>
				</div>
				<div className="row">
					<div className="col-xs-2 col-xs-offset-2">
						<div className="list-group">

							{/* 原生html中,靠<a>跳转不同的页面 */}
							{/* <a className="list-group-item" href="./about.html">About</a>
							<a className="list-group-item active" href="./home.html">Home</a> */}

							{/* 在React中靠路由链接实现切换组件--编写路由链接 路由链接能引起路由的变化*/}
							{/* 浏览器不认识link标签 link标签是react中的 所以最终link标签会转成a标签 to属性转成href属性 */}
							{/* 下面一行代码会转成这个:<a class="list-group-item" href="/about">About</a> */}
							<Link className="list-group-item" to="/about">About</Link>
							<Link className="list-group-item" to="/home">Home</Link>
						</div>
					</div>
					<div className="col-xs-6">
						<div className="panel">
							<div className="panel-body">
								{/* 注册路由:编写这个路径对应这个路由 即路由/链接与组件的关系 */}
								{/* 如果你的路径变成了/about 展示About这个组件 About是你最上面用import引入的那个组件 */}
								<Route path="/about" component={About}/>
								<Route path="/home" component={Home}/>
							</div>
						</div>
					</div>
				</div>
			</div>
		)
	}
}

Link和Route标签都必须被路由器管理(被Router包裹),路由器有两种:BrowserRouter和HistoryRouter,BrowserRouter较常用,本文除特殊说明外都使用BrowserRouter。index.js代码如下:

import React from 'react'//引入react核心库
import ReactDOM from 'react-dom'//引入ReactDOM
import {BrowserRouter} from 'react-router-dom'
import App from './App'//引入App

ReactDOM.render(
	// 在APP.jsx中用到的Link标签和Route标签都必须包裹在Router中,也就是说路由需要被一个路由器管理
	// 所以在index.js中 用这一个路由器直接包裹App
	<BrowserRouter>
		<App/>
	</BrowserRouter>,
	document.getElementById('root')
)
  1. BrowserRouter与HashRouter的区别
    (1)底层原理不一样:
    BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。
    HashRouter使用的是URL的哈希值。
    (2)path表现形式不一样
    BrowserRouter的路径中没有#,例如:localhost:3000/demo/test
    HashRouter的路径包含#,例如:localhost:3000/#/demo/test
    (3)刷新后对路由state参数的影响
    BrowserRouter没有任何影响,因为state保存在history对象中。
    HashRouter刷新后会导致路由state参数的丢失!!!因为他没有借助history对象来保存
    (4)备注:HashRouter可以用于解决一些路径错误相关的问题。
  2. 路由组件与一般组件
    (1)一般组件使用时<Home />,路由组件使用时<Route path="/home" component={Home}/>
    (2)一般组件放在components文件夹下,路由组件放在pages文件夹下
    (3)在不传参数的情况下,路由组件About会收到路由器传递的参数,一般组件Header收不到任何参数
    在这里插入图片描述
    在这里插入图片描述
    其实,history.location = location,并且location.pathame、match.path、match.url都可以获取当前路由的路径
    在这里插入图片描述

NavLink的使用

我们希望点击页面上的路由,给link标签追加一个类名,让选项卡在点击时呈现不一样的状态。那就不要用之前的link标签,用Link标签的升级版NavLink。App.jsx中的代码:

<div className="row">
	<div className="col-xs-2 col-xs-offset-2">
		<div className="list-group">
			{/* 在React中靠路由链接实现切换组件--编写路由链接 */}
			{/* NavLink有个属性activeClassName 当点击NavLink时,会将activeClassName的值追加到className属性中*/}
			{/* 你可以新建一个.css文件 然后将atguigu这个样式写在里面
			你也可以写在index.html中 因为最后所有的东西都会放在index.html中 */}
			<NavLink activeClassName="atguigu" className="list-group-item" to="/about">About</NavLink>
			<NavLink activeClassName="atguigu" className="list-group-item" to="/home">Home</NavLink>
		</div>
	</div>
	<div className="col-xs-6">
		<div className="panel">
			<div className="panel-body">
				{/* 注册路由 */}
				<Route path="/about" component={About}/>
				<Route path="/home" component={Home}/>
			</div>
		</div>
	</div>
</div>

封装NavLink:自定义MyNavLink组件

在这里插入图片描述
如果我们要编写很多的路由链接,就需要写很多上面的代码,会产生很多的冗余代码,所以我们可以把NavLink封装起来,自定义MyNavLink组件,以后只用写下面的代码:
在这里插入图片描述
将app.jsx中编写路由链接的代码改为以下:to和title都是传给MyNavLink组件的参数
在这里插入图片描述
MyNavLink组件的代码如下:从props中获取参数(通过解构赋值获取的) 然后展示出来,props本身就是属性的意思,上面传参数也是作为组件的属性传过来的,props就是用来接受标签属性的
在这里插入图片描述
但是如果我们要传100个参数,我们又要写100个to="/about" title="About" a="a"……,所以我们可以批量传数据过去,你在app.jsx中不管传多少数据过去,都会在props中
在这里插入图片描述
所以在MyNavLink组件中,可以直接通过解构赋值获得数据,但是其他数据放在标签中作为属性 ,title是放在标签体中,放的位置不一样,所以title还是需要单独获取出来。
在这里插入图片描述
你现在看我们之前在app.jsx中使用NavLink标签的代码:
在这里插入图片描述
是不是不是自闭和的标签 但是我们现在封装的MyNavLink组件是自闭和的,需要靠传入的title属性来决定标签体展示什么
在这里插入图片描述
但是我们要怎么接收标签体内容呢?propos只能接收标签属性,注意:标签体内容也是一个特殊的标签属性,键是children,值是标签体中的内容。
在这里插入图片描述
现在我们MyNavLink组件中的代码可以写成:
在这里插入图片描述
NavLink标签中的内容,即标签体内容{this.props.children},是标签的children属性的值,当我们写一个标签时,不写标签体内容,而是给标签写children属性,也可以指定标签体中的内容,所以MyNavLink组件中的代码可以写成,直接解构传过来的参数就完事:
在这里插入图片描述

switch的使用

如果我们在注册路由时,写了下面的代码,会出现什么情况:点击路由后,会展示Home、Test组件
在这里插入图片描述
当你的路径变成/home时,他会一个个匹配你注册的路由:第一个路径不同,继续匹配下一个,路径相同,匹配上了,展示相应的组件,虽然匹配上了,但是他还会继续匹配,下一个路径又匹配上了,又展示相应的组件
在这里插入图片描述
如果我写的路由特别多(如下图),按照上面的匹配规则,效率特别低,如果我们现在的路径是/about,他匹配完了第一行,又会继续匹配剩下的路由,没必要啊,我们怎么让他匹配到了就终止呢
在这里插入图片描述
我们可以使用switch组件,把注册的所有路由包裹起来,如果你不用switch,你找到了路径还会继续往下找,用了switch之后,你找到了路径就不会继续往下找了,当然,像下面这种情况(下面的代码在switch组件中),也就只会展示Home组件,但是一般情况下,我们就是一个路径对应一个组件啊,一般不会出现下面的情况
在这里插入图片描述

解决多级结构刷新页面样式丢失问题

我们现在使用的是BrowserRouter,希望路径变成/atguigu/about后展示About组件,变成/atguigu/home后展示Home组件,App.jsx中的代码:

<div className="row">
	<div className="col-xs-2 col-xs-offset-2">
		<div className="list-group">
			{/* 在React中靠路由链接实现切换组件--编写路由链接 */}
			<MyNavLink to="/atguigu/about">About</MyNavLink>
			<MyNavLink to="/atguigu/home">Home</MyNavLink>
		</div>
	</div>
	<div className="col-xs-6">
		<div className="panel">
			<div className="panel-body">
				{/* 注册路由 */}
				<Switch>
					<Route path="/atguigu/about" component={About}/>
					<Route path="/atguigu/home" component={Home}/>
				</Switch>
			</div>
		</div>
	</div>
</div>

虽然我们实现了这个功能,页面也可以正常展示,但是当我们刷新页面后,页面样式就没有了,页面样式是bootstrap提供的,也就是说找不到bootstrap的资源了,bootstrap在我们项目的public/css/bootstrap.css中,也就是说我们通过访问http://localhost:3000/css/bootstrap.css获取资源,现在刷新了获取不到资源了,因为现在他会去http://localhost:3000/atguigu/css/bootstrap.css获取资源,我们没有atguigu这个文件夹,所以获取不到。
在解决这个问题前,我们先了解一下http://localhost:3000/css/bootstrap.csshttp://localhost:3000是我们用devServer开起来的服务器,就是我们的脚手架。react的脚手架中,通过webpack配置了一个东西,使得public文件夹就是http://localhost:3000这台内置服务器的根路径,所以上述其实访问的是public/css/bootstrap.css。
如果你请求的资源不存在,那么我会把publi/index.html返回给你
如何让页面样式不丢失呢?
(1)public/index.html中引入bootstrap时,不要写<link rel="stylesheet" href="./css/bootstrap.css">,要写<link rel="stylesheet" href="/css/bootstrap.css">,因为href="./css/bootstrap.css"表示从当前文件出发,去当前文件夹下找,此时会把/atguigu带上,请求的资源就变成了http://localhost:3000/atguigu/css/bootstrap.css,而href="/css/bootstrap.css"表示从http://localhost:3000下去找/css/bootstrap.css,即请求http://localhost:3000/css/bootstrap.css这个资源
(2)public/index.html中引入bootstrap时,不要写<link rel="stylesheet" href="./css/bootstrap.css">,要写:<link rel="stylesheet" href="%PUBLIC_URL%/css/bootstrap.css">%PUBLIC_URL%代表public的绝对路径,所以其实还是去public/css/bootstrap.css中获取资源。(这种方法只有在react中可以使用)
(3)public/index.html中引入bootstrap时,还是写<link rel="stylesheet" href="./css/bootstrap.css">,但是在index.js中使用HashRouter,因为HashRouter会多一个#,#后边的内容都被认为时前端的资源,不会出现在HTTP请求中,根本不会带给服务器,index.js中代码如下:(这种方法不常用,因为我们一般都用BrowserRouter)

import React from 'react';//引入react核心库
import ReactDOM from 'react-dom';//引入ReactDOM
import {BrowserRouter, HashRouter} from 'react-router-dom'
import App from './App'//引入App
ReactDOM.render(
	<HashRouter>
		<App/>
	</HashRouter>,
	document.getElementById('root')
)

路由的模糊匹配与严格匹配

假如现在点击一个链接,路径变成了/home/a/b,但是我在注册路由时,/home才展示Home组件,app.jsx中代码如下所示,此时还是能匹配上的,这就是模糊匹配。其实他在匹配的时候,会先匹配home,匹配上了展示Home组件,然后匹配a,没找到/a这个路径,结束匹配,所以如果编写路由链接时写的是<MyNavLink to="/a/home/b">Home</MyNavLink>,一个路由也匹配不上,他一上来没找到/a路径,直接结束匹配。

<div className="row">
	<div className="col-xs-2 col-xs-offset-2">
		<div className="list-group">
			{/* 在React中靠路由链接实现切换组件--编写路由链接 */}
			<MyNavLink to="/about">About</MyNavLink>
			<MyNavLink to="/home/a/b">Home</MyNavLink>
			{/* 但是如果你写的是:<MyNavLink to="/a/home/b">Home</MyNavLink>
				还是匹配不上,点击后不会展示Home组件*/}
		</div>
	</div>
	<div className="col-xs-6">
		<div className="panel">
			<div className="panel-body">
				{/* 注册路由 */}
				<Switch>
					<Route path="/about" component={About}/>
					<Route path="/home" component={Home}/>
				</Switch>
			</div>
		</div>
	</div>
</div>

精准匹配就是:编写路由链接的路经和注册路由的路径一致
在这里插入图片描述
开启精准匹配:给Route标签添加exact={true}属性或直接写exact
开启严格匹配的原则:没有耽误页面的呈现,没有引发其他的问题,不开启,如果不开启会出bug,那么就开启。

<div className="row">
	<div className="col-xs-2 col-xs-offset-2">
		<div className="list-group">
			{/* 在React中靠路由链接实现切换组件--编写路由链接 */}
			<MyNavLink to="/about">About</MyNavLink>
			<MyNavLink to="/home/a/b">Home</MyNavLink>
		</div>
	</div>
	<div className="col-xs-6">
		<div className="panel">
			<div className="panel-body">
				{/* 注册路由 */}
				<Switch>
					<Route exact path="/about" component={About}/>
					<Route exact path="/home" component={Home}/>
				</Switch>
			</div>
		</div>
	</div>
</div>

Redirect的使用

希望一打开页面,就帮我默认选中一个选项卡,并展示相应的组件,使用重定向来实现。
http://localhost:3000等同于http://localhost:3000/,只不过在地址栏中,这个/被隐藏了,那么既然有/,他还是会去注册的路由中查找路径为/的路由的,但是我们前面的代码没注册过这种路由,所以他找不到,页面不展示任何组件。可以使用Redirect组件设置,没有匹配得上的路径时,跳转去哪里:

<div className="row">
	<div className="col-xs-2 col-xs-offset-2">
		<div className="list-group">
			{/* 在React中靠路由链接实现切换组件--编写路由链接 */}
			<MyNavLink to="/about">About</MyNavLink>
			<MyNavLink to="/home">Home</MyNavLink>
		</div>
	</div>
	<div className="col-xs-6">
		<div className="panel">
			<div className="panel-body">
				{/* 注册路由 */}
				<Switch>
					<Route path="/about" component={About}/>
					<Route path="/home" component={Home}/>
					{/* Redirect标签写在所有路由的最后面,前面的路径都不匹配,
					就去Redirect标签的to指向的路径 */}
					<Redirect to="/about"/>
				</Switch>
			</div>
		</div>
	</div>
</div>

嵌套路由/多级路由

我们现在想实现这样的功能,点击页面左栏Home这个选项卡,路径变为/home,展示右边的内容,在右边点击News选项卡,展示news组件的内容,点击Message选项卡,展示Message组件的内容。(如下图所示,红色框是导航区,蓝色框是展示区)
在这里插入图片描述

  1. 项目结构:(如下图所示)
    (1)项目的App.jsx中编写了/about和/home路由链接并注册这两个路由
    (2)项目的page文件夹中存放About和Home组件的代码,在Home组件存放News和Message组件的代码,并且在Home组件的index.js中编写News和Message组件的路由链接并注册这两个路由
    在这里插入图片描述
  2. App.jsx中的代码如下:
<div className="row">
	<div className="col-xs-2 col-xs-offset-2">
		<div className="list-group">
			{/* 在React中靠路由链接实现切换组件--编写路由链接 */}
			<MyNavLink to="/about">About</MyNavLink>
			<MyNavLink to="/home">Home</MyNavLink>
		</div>
	</div>
	<div className="col-xs-6">
		<div className="panel">
			<div className="panel-body">
				{/* 注册路由 */}
				<Switch>
					<Route path="/about" component={About}/>
					<Route path="/home" component={Home}/>
					<Redirect to="/about"/>
				</Switch>
			</div>
		</div>
	</div>
</div>
  1. 在Home组件的index.js中如何编写News和Message组件的路由链接呢?如果是写<MyNavLink to="/news">News</MyNavLink>,那么点击后路径会变为http://localhost:3000/news,会触发路由的匹配,每次路由的匹配都是从最开始注册到最后注册的流程走下去的,App.jsx中的路由是最先注册的,所以他会先去App.jsx中注册的路由中匹配路径,此时我们的路径是/news,但是看上面的代码我们压根没有路径为/news的组件,最后由于重定向,路径变为/about,所以子组件的路径必须带上父组件路径,写为<MyNavLink to="/home/news">News</MyNavLink>,具体代码如下:
<div>
	<h3>我是Home的内容</h3>
	<div>
		<ul className="nav nav-tabs">
			<li>
				<MyNavLink to="/home/news">News</MyNavLink>
			</li>
			<li>
				<MyNavLink to="/home/message">Message</MyNavLink>
			</li>
		</ul>
		{/* 注册路由 */}
		<Switch>
			<Route path="/home/news" component={News}/>
			<Route path="/home/message" component={Message}/>
			<Redirect to="/home/news"/>
		</Switch>
	</div>
</div>

点击News选项卡后路径变为/home/news,触发路由的匹配,先去App.jsx中注册的路由中匹配路径,由于模糊匹配,路径/home/news和/home匹配上了,展示Home组件,Home组件就要挂载上去,Home组件中的index.js文件就执行了,index.js又注册了路由,继续匹配路由,我们的路径是/home/news,index.js注册的路由也有路径为/home/news的组件,匹配上了,展示News组件

  1. 嵌套路由/多级路由必须规规矩矩带上父组件的路径

向路由组件传递参数数据

我们现在有个页面,点击页面左栏Home这个选项卡,路径变为/home,展示右边的内容,在右边点击Message选项卡,展示message组件的内容(展示Message01、Message03、Message06),点击Message01/03/06,在下方展示数据,展示的数据内容不一样,但是格式一样。(如下图所示,红色框是导航区,蓝色框是展示区)
在这里插入图片描述
我们把下面这个组件定义为Detail,点击Message01/03/06都展示Detail组件。
在这里插入图片描述
项目结构如下:
在这里插入图片描述
由于点击Message01/03/06,在下方展示的数据内容不一样,但是格式一样。所以我们可以点击Message01/03/06的时候给Detail组件传递参数,Detail组件根据参数展示相应的数据

不论怎么传递参数都有三个步骤:
(1)怎么携带
(2)怎么声明接收
(3)怎么具体接收/怎么取出使用

传递params参数

  1. 编写路由链接时带上参数:to="/home/message/detail/xx",xx就是参数,这样就能让链接上带上参数
  2. 注册路由时接收参数:path="/home/message/detail/:id",以后这个参数就存在该路由(/home/message/detail)的id变量中

Message的index.js的代码:

import React, { Component } from 'react'
import {Link,Route} from 'react-router-dom'
import Detail from './Detail'

export default class Message extends Component {
	state = {
		messageArr:[
			{id:'01',title:'消息1'},
			{id:'02',title:'消息2'},
			{id:'03',title:'消息3'},
		]
	}
	render() {
		const {messageArr} = this.state
		return (
			<div>
				<ul>
					{
						messageArr.map((msgObj)=>{
							return (
								<li key={msgObj.id}>
									{/* 1. 向路由组件传递params参数
									之前的代码:<Link to="/home/message/detail">{msgObj.title}</Link>
									to后面跟的是个字符串 但是我们现在的参数是msgObj.id是个变量,所以用模板字符串
									模板字符串是js里的东西,要使用得用{}包裹 在模板字符串中字符串和变量拼接要用${msgObj.id}
									*/}
									<Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>
								</li>
							)
						})
					}
				</ul>
				<hr/>
				{/* 2. 声明接收params参数 声明后,Detail就能获得参数,你前面那样写只能说明点击连接后携带了参数,
				下面这样写才让组件收到了参数,接收到第一个参数放在id里,第二个参数放在title里*/}
				<Route path="/home/message/detail/:id/:title" component={Detail}/>
			</div>
		)
	}
}
  1. 在Detail组件中打印this.props发现这个id就存在this.props.match.params中,如下图所示
    在这里插入图片描述
    Detail组件可以取出参数使用,Detail的index.js中代码如下:
import React, { Component } from 'react'

const DetailData = [
	{id:'01',content:'你好,中国'},
	{id:'02',content:'你好,尚硅谷'},
	{id:'03',content:'你好,未来的自己'}
]
export default class Detail extends Component {
	render() {
		console.log(this.props);
		// 3. 接收到得参数放在this.props.match.params中,获取params参数
		const {id,title} = this.props.match.params
		const findResult = DetailData.find((detailObj)=>{
			return detailObj.id === id
		})
		return (
			<ul>
				<li>ID:{id}</li>
				<li>TITLE:{title}</li>
				<li>CONTENT:{findResult.content}</li>
			</ul>
		)
	}
}

传递search参数

  1. 编写路由链接时带上参数:to="/home/message/detail/?id=xx&title=xxx",xx和xxx就是参数,id和title时变量名,这样就能让链接上带上参数,?标识这是传递search参数,无需声明接收,正常注册路由即可
  2. 注册路由时接收参数:path="/home/message/detail"

Message的index.js的代码:

import React, { Component } from 'react'
import {Link,Route} from 'react-router-dom'
import Detail from './Detail'

export default class Message extends Component {
	state = {
		messageArr:[
			{id:'01',title:'消息1'},
			{id:'02',title:'消息2'},
			{id:'03',title:'消息3'},
		]
	}
	render() {
		const {messageArr} = this.state
		return (
			<div>
				<ul>
					{
						messageArr.map((msgObj)=>{
							return (
								<li key={msgObj.id}>
									{/* 向路由组件传递search参数,这个?就代表了要传递search参数,search参数无需声明接收 */}
									<Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link>

								</li>
							)
						})
					}
				</ul>
				<hr/>
				{/* search参数无需声明接收,正常注册路由即可 */}
				<Route path="/home/message/detail" component={Detail}/>
			</div>
		)
	}
}
  1. 在Detail组件中打印this.props发现传递过来的参数存在this.props.location.search中,如下图所示
    在这里插入图片描述
    但是这个参数是?id=xx&title=xxx这种形式,这种键值对用等号分割,多个键值对用&连接的编码方式称为urlencoded,我们可以使用querystring这个库进行对象和urlencoded相互转换,react脚手架已经下载好了,直接import引入使用即可,但是由于参数是?id=xx&title=xxx,多了个?,所以我们需要先截取再转换,Detail的index.js中代码如下:
import React, { Component } from 'react'
import qs from 'querystring'// 用来进行对象和urlencoded相互转换的库
// querystring的使用
let obj = {name:'tom',age:18}
console.log(qs.stringify(obj))// name=tom&age=18 这就是urlencoded编码
let str = "car=奔驰&price=199";
console.log(qs.parse(str))// {car:"奔驰",price:199}

const DetailData = [
	{id:'01',content:'你好,中国'},
	{id:'02',content:'你好,尚硅谷'},
	{id:'03',content:'你好,未来的自己'}
]
export default class Detail extends Component {
	render() {
		console.log(this.props);
		// 接收search参数
		const {search} = this.props.location
		const {id,title} = qs.parse(search.slice(1))// 因为我们获取的search参数开头还带了个? 所以这里先截取一下再转换

		const findResult = DetailData.find((detailObj)=>{
			return detailObj.id === id
		})
		return (
			<ul>
				<li>ID:{id}</li>
				<li>TITLE:{title}</li>
				<li>CONTENT:{findResult.content}</li>
			</ul>
		)
	}
}

传递state参数

注意:这个state是路由组件身上独有的属性,不是组件里的状态。

  1. 之前编写路由链接时,to一直写的是字符串,如果你要传state参数,to要写成对象,但是在jsx中{}代表里面要写js语法,所以你要写对象要包两层{{在这里面写键值对}}(里面那个{在这里面写键值对}才是对象),对象里面的属性pathname代表点击后要跳转的链接地址,state里面存放你要传递的数据,他的值也是个对象:to={{pathname:'/home/message/detail',state:{键值对}}}
  2. 因为时传递state参数,注册路由时无需声明接收,正常注册路由即可

Message的index.js的代码:

import React, { Component } from 'react'
import {Link,Route} from 'react-router-dom'
import Detail from './Detail'

export default class Message extends Component {
	state = {
		messageArr:[
			{id:'01',title:'消息1'},
			{id:'02',title:'消息2'},
			{id:'03',title:'消息3'},
		]
	}
	render() {
		const {messageArr} = this.state
		return (
			<div>
				<ul>
					{
						messageArr.map((msgObj)=>{
							return (
								<li key={msgObj.id}>
									{/* 向路由组件传递state参数 */}
									<Link to={{pathname:'/home/message/detail',state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}</Link>
								</li>
							)
						})
					}
				</ul>
				<hr/>
				{/* state参数无需声明接收,正常注册路由即可 */}
				<Route path="/home/message/detail" component={Detail}/>
			</div>
		)
	}
}
  1. 在Detail组件中打印this.props发现传递过来的参数存在this.props.location.state中,如下图所示
    在这里插入图片描述
    由于我们现在使用的是BrowserRouter,他一直在操作浏览器中的history,state是history的一个属性,所以他其实有记录的,所以刷新也可以保留参数,但是如果你把缓存清空了/把浏览器关了,再打开http://localhost:3000/home/message/detail就没有参数了,并且会报错,为了避免这种情况,在获取不到参数时,我们让他为空对象。Detail的index.js中代码如下:
import React, { Component } from 'react'

const DetailData = [
	{id:'01',content:'你好,中国'},
	{id:'02',content:'你好,尚硅谷'},
	{id:'03',content:'你好,未来的自己'}
]
export default class Detail extends Component {
	render() {
		console.log(this.props);

		// 接收state参数
		const {id,title} = this.props.location.state || {}

		const findResult = DetailData.find((detailObj)=>{
			return detailObj.id === id
		}) || {}
		return (
			<ul>
				<li>ID:{id}</li>
				<li>TITLE:{title}</li>
				<li>CONTENT:{findResult.content}</li>
			</ul>
		)
	}
}

路由跳转的两种模式

路由其实就是对浏览器的历史记录进行操作,一共有两个操作,一个是push(压栈的方式,一个压一个,会留下痕迹),一个是replace(替换掉目前这个)。路由跳转默认使用push,如何让路由跳转为replace:Link、NavLink标签有个属性replace,将其设为true就是使用replace模式。Message的index.js的代码:

return (
	<li key={msgObj.id}>
		{/* 向路由组件传递state参数 */}
		<Link replace to={{pathname:'/home/message/detail',state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}</Link>
	</li>
)

编程式路由导航

  1. 我们现在点击选项卡可以切换路由,靠的是Link、NavLink标签。现在我想实现:进入news组件3s后,自动跳转到message组件,如何实现?你之前用的Link、NavLink标签首先要有人点击他才会跳转,我们如何在不借助路由链接(Link、NavLink标签)的前提下,写一些代码实现路由跳转?这些代码就称为编程式路由导航。
  2. 我们现在有个页面,点击页面左栏Home这个选项卡,路径变为/home,展示右边的内容,在右边点击Message选项卡,展示message组件的内容(展示消息1/2/3),消息1/2/3右边有两个按钮,点击第一个push查看消息1/2/3中的内容,点击第二个replace查看消息1/2/3中的内容,在下方展示数据(如下图所示)
    在这里插入图片描述
  3. 前面我们说了,我们是通过操作浏览器中的history实现的路由跳转,那么我们同样可以通过直接操作history实现路由跳转,history中的api如图所示
    在这里插入图片描述
  4. 同理,你也可以写两个按钮,一个前进一个后退,通过直接操作history的API实现前进后退,无需点浏览器左上角的前进后退按钮。Message的index.js的代码:
import React, { Component } from 'react'
import {Link,Route} from 'react-router-dom'
import Detail from './Detail'

export default class Message extends Component {
	state = {
		messageArr:[
			{id:'01',title:'消息1'},
			{id:'02',title:'消息2'},
			{id:'03',title:'消息3'},
		]
	}

	replaceShow = (id,title)=>{
		//replace跳转+携带params参数
		//this.props.history.replace(`/home/message/detail/${id}/${title}`)

		//replace跳转+携带search参数
		// this.props.history.replace(`/home/message/detail?id=${id}&title=${title}`)

		//replace跳转+携带state参数 replace/push的第二参数可选,就是state参数
		// {id,title}是{id:id,title:title}的缩写
		this.props.history.replace(`/home/message/detail`,{id,title})
	}

	pushShow = (id,title)=>{
		//push跳转+携带params参数
		// this.props.history.push(`/home/message/detail/${id}/${title}`)

		//push跳转+携带search参数
		// this.props.history.push(`/home/message/detail?id=${id}&title=${title}`)

		//push跳转+携带state参数
		this.props.history.push(`/home/message/detail`,{id,title})
		
	}

	back = ()=> this.props.history.goBack()
	forward = ()=> this.props.history.goForward()
	go = ()=> this.props.history.go(-2)// go(n):n>0,前几n步,n<0,后退n步
	
	render() {
		const {messageArr} = this.state
		return (
			<div>
				<ul>
					{
						messageArr.map((msgObj)=>{
							return (
								<li key={msgObj.id}>

									{/* 向路由组件传递params参数 */}
									{/* <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link> */}

									{/* 向路由组件传递search参数 */}
									{/* <Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link> */}

									{/* 向路由组件传递state参数 */}
									<Link to={{pathname:'/home/message/detail',state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}</Link>

									&nbsp;<button onClick={()=> this.pushShow(msgObj.id,msgObj.title)}>push查看</button>
									&nbsp;<button onClick={()=> this.replaceShow(msgObj.id,msgObj.title)}>replace查看</button>
								</li>
							)
						})
					}
				</ul>
				<hr/>
				{/* 声明接收params参数 */}
				{/* <Route path="/home/message/detail/:id/:title" component={Detail}/> */}

				{/* search参数无需声明接收,正常注册路由即可 */}
				{/* <Route path="/home/message/detail" component={Detail}/> */}

				{/* state参数无需声明接收,正常注册路由即可 */}
				<Route path="/home/message/detail" component={Detail}/>

				<button onClick={this.back}>回退</button>&nbsp;
				<button onClick={this.forward}>前进</button>&nbsp;
				<button onClick={this.go}>go</button>
			</div>
		)
	}
}
  1. 实现:进入news组件2s后,自动跳转到message组件
import React, { Component } from 'react'

export default class News extends Component {
	componentDidMount(){// 组件一创建,开启定时器,2s后跳转到message组件
		setTimeout(()=>{
			this.props.history.push('/home/message')
		},2000)
	} 
	render() {
		return (
			<ul>
				<li>news001</li>
				<li>news002</li>
				<li>news003</li>
			</ul>
		)
	}
}

withRouter的使用

只有路由组件才有history对象,才能像上面那样直接操作history的api实现路由的跳转,一般组件没有,我们可以通过withRouter让一般组件也有history对象。withRouter是react-router-dom身上的一个函数,他接收一个一般组件,然后就给这个一般组件的身上加上了路由组件所特有的API,返回一个新组件。比如说我们现在想把前面的前进后退按钮放在Header组件中,你就把Header组件当成路由组件来用,最后导出时用withRouter包裹以下让他变成路由组件即可,前进后退功能依旧能用

import React, { Component } from 'react'
import {withRouter} from 'react-router-dom'

class Header extends Component {
	back = ()=> this.props.history.goBack()
	forward = ()=> this.props.history.goForward()
	go = ()=> this.props.history.go(-2)
	
	render() {
		console.log('Header组件收到的props是',this.props);
		return (
			<div className="page-header">
				<h2>React Router Demo</h2>
				<button onClick={this.back}>回退</button>&nbsp;
				<button onClick={this.forward}>前进</button>&nbsp;
				<button onClick={this.go}>go</button>
			</div>
		)
	}
}
export default withRouter(Header)

antd

antd的基本使用

antd是react的UI组件库,GitHub地址

  1. 安装:npm install antd --save
  2. 从库中import你要用的东西,在你要用的东西那一页都有
  3. 复制代码粘贴到你想放置的地方
  4. 可以在页面的API中进行其他配置

UI组件库:elementUI(饿了么)、antd(蚂蚁)、vant(常用于移动端,有赞团队)

antd样式的按需引入

在node_modules/antd/dist/antd.css中放着antd的所有样式,要想使用antd,就必须引入这个样式:import 'antd/dist/antd.css',但是他很大,没必要全部引入,因为有些样式你可能压根没用到,你可以按需引入,用到哪个组件就引入哪个组件的样式(在官网看3.x的文档更详细),需要修改create-react-app的默认配置,有两种方法:

  1. create-react-app是react脚手架自己生成的默认配置,我们要想修改,首先需要将脚手架的配置暴露出来,暴露出来后就隐藏不了了,操作不可逆。所以我们在桌面进入cmd,输入create-react-app demo创建一个新的脚手架,webstorm打开新的脚手架,在终端输入react-scripts eject,在config/webpack.config.js中修改,但是这样一旦改错了,脚手架就起不来了
  2. 自己写代码,让customize-cra去帮我们改,首先要下载customize-cra,在终端输入:yarn add create-react-app customize-cra,然后在config-overrides.js中写代码,用customize-cra这个库按照config-overrides.js中的内容修改了脚手架,就不能再用脚手架原来的启动命令了,你必须用create-react-app来启动,在package.json中将"scripts"修改为:
"scripts": {
     "start": "react-app-rewired start",
     "build": "react-app-rewired build",
     "test": "react-app-rewired test",
     "eject": "react-scripts eject"
   },

以后在终端还是通过npm start来启动项目,app.jsx代码如下:

import React, { Component } from 'react'
import { Button,DatePicker } from 'antd';// 像这种不写路径直接写包名的,他会直接去node_modules中找
// 在node_modules/antd/dist/antd.css中放着antd的所有样式
// import 'antd/dist/antd.css'
import {WechatOutlined,WeiboOutlined,SearchOutlined} from '@ant-design/icons'
const { RangePicker } = DatePicker;

export default class App extends Component {
	render() {
		return (
			<div>
				App....
				<button>点我</button>
				<Button type="primary">按钮1</Button>
				<Button >按钮2</Button>
				<Button type="link">按钮3</Button>
				<Button type="primary" icon={<SearchOutlined />}>
					Search
				</Button>
				<WechatOutlined />
				<WeiboOutlined />
				<DatePicker/>
				<RangePicker/>
			</div>
		)
	}
}

然后在package.json的同级目录中创建config-overrides.js,在config-overrides.js中写代码:

const { override, fixBabelImports} = require('customize-cra');

module.exports = override(
  fixBabelImports('import', {// 按需引入,这里些import
    libraryName: 'antd',// 按需引入的库antd
    libraryDirectory: 'es',// antd中用了es的模块化规范
    style: css//你要对css进行按需引入
	})
);

antd自定义主题

antd的样式都使用less写的,要想改antd的样式,需要自己改less文件然后编译成css,所以首先在终端安装less和less-loader:yarn add less less-loader,然后通过customize-cra中提供的less相关函数addLessLoader来帮助加载less样式,同时修改config-overrides.js中的代码如下:

const { override, fixBabelImports,addLessLoader} = require('customize-cra');

module.exports = override(
  fixBabelImports('import', {// 按需引入,这里些import
    libraryName: 'antd',// 按需引入的库antd
    libraryDirectory: 'es',// antd中用了es的模块化规范
    style: true,// 自定义主题时写这行
	}),
	//自定义主题写下面的代码:
	addLessLoader({
		lessOptions:{
			javascriptEnabled: true,// 允许用js修改底层的less文件
			modifyVars: { '@primary-color': 'green' },// 修改哪些变量 其他变量参考官网
		}
	}),
);

redux

reduxgithub

  1. redux是一个专门用于做状态管理的JS库(不是react插件库)。
  2. 它可以用在react, angular, vue等项目中, 但基本与react配合使用。
  3. 作用: 集中式管理react应用中多个组件共享的状态。
  4. 什么情况下需要使用redux:
    (1)某个组件的状态,需要让其他组件可以随时拿到(共享)。
    (2)一个组件需要改变另一个组件的状态(通信)。
    (3)总体原则:能不用就不用, 如果不用比较吃力才考虑使用。
  5. redux工作流程:
    在这里插入图片描述
    页面上有个展示区,展示当前的数字,初始值为0,有个下拉框,下拉框中有一些数字,页面上还有四个按钮:+、-、×、÷,你点击下拉框中的数字后点某个按钮,就会执行当前数字+/-/×/÷你点击的数字。假如现在你在下拉框中选中了2,然后点了+,如果下拉框、按钮、当前数字都在组件本身,那么你可以自己处理了,但是现在当前数字在redux中,那你需要通知redux将值+2,你需要把值+2这件事告诉action creators,action creators用来创建action,action被称为动作对象,+/-/×/÷这都叫动作,动作对象包含着你本次的动作类型type及你本次操作的数据data,然后由dispatch将action分发下去,送给store,dispatch是个函数,接收的参数就是action,store相当于redux的指挥者,store将之前的状态和action一起交给reducers,reducers根据action执行完操作后将新状态返回给store,之后,组件可以通过getState()从store中获取状态。
    reducers不仅可以加工状态,还可以初始化状态,前面我们讲的就是加工状态,初始化状态时action的type就是初始化状态@@init@@,data是初始化的值(也可以不传),dispatch将action送给store,store将undefined和action一起交给reducers,传递的就不是之前的状态了。
    (1)action:动作的对象,包含2个属性,例:{ type: 'ADD_STUDENT',data:{name: 'tom',age:18} }
    type:标识属性, 值为字符串, 唯一, 必要属性
    data:数据属性, 值类型任意, 可选属性
    (2)reducer:用于初始化状态、加工状态。加工时,根据旧的state和action, 产生新的state的纯函数。每个组件都有一个reducer
    (3)store:将state、action、reducer联系在一起的对象

如何得到此对象?

  1. import {createStore} from ‘redux’
  2. import reducer from ‘./reducers’
  3. const store = createStore(reducer)

此对象的功能?

  1. getState(): 得到state
  2. dispatch(action): 分发action, 触发reducer调用, 产生新的state
  3. subscribe(listener): 注册监听, 当产生了新的state时, 自动调用
  1. redux的安装:终端输入:yarn add redux

求和案例

需求:页面上有个展示区,展示当前的数字,初始值为0,有个下拉框,下拉框中有一些数字,页面上还有四个按钮:+、-、当前数字为奇数则加、异步加,你点击下拉框中的数字后点+/-按钮,就会执行当前数字+/-你点击的数字

纯react版本

在组件内部实现,不适用redux,点击按钮后执行方法,该方法对组件内的数据进行操作

import React, { Component } from 'react'

export default class Count extends Component {
	state = {count:0}

	//加法
	increment = ()=>{
		const {value} = this.selectNumber// 字符串,后面*1将他转为数字
		const {count} = this.state
		this.setState({count:count+value*1})
	}
	//减法
	decrement = ()=>{
		const {value} = this.selectNumber
		const {count} = this.state
		this.setState({count:count-value*1})
	}
	//奇数再加
	incrementIfOdd = ()=>{
		const {value} = this.selectNumber
		const {count} = this.state
		if(count % 2 !== 0){
			this.setState({count:count+value*1})
		}
	}
	//异步加
	incrementAsync = ()=>{
		const {value} = this.selectNumber
		const {count} = this.state
		setTimeout(()=>{
			this.setState({count:count+value*1})
		},500)
	}

	render() {
		return (
			<div>
				<h1>当前求和为:{this.state.count}</h1>
				<select ref={c => this.selectNumber = c}>
					<option value="1">1</option>
					<option value="2">2</option>
					<option value="3">3</option>
				</select>&nbsp;
				<button onClick={this.increment}>+</button>&nbsp;
				<button onClick={this.decrement}>-</button>&nbsp;
				<button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>&nbsp;
				<button onClick={this.incrementAsync}>异步加</button>&nbsp;
			</div>
		)
	}
}

redux精简版

不使用action creators创建action,我们自己创建action,action就是一个对象。你在创建store的时候就已经有reducer了,你可以把store看成饭店老板,reducer就是厨师,店都开好了,老板肯定已经找好厨师了,所以我们先创建reducer。reducer可以初始化状态和加工状态,他会接收到store传给他的旧状态和action,然后对旧状态进行处理返回新状态给store,在js的世界里,谁能接收参数,然后处理逻辑,并返回结果?数组肯定不行吧,对象也不行吧,只有函数,所以reducer是个函数。reducer函数会接到两个参数:之前的状态(preState),动作对象(action)。count_reducer代码如下:

const initState = 0 //初始化状态
export default function countReducer(preState=initState,action){
	// console.log(preState);
	//从action对象中获取:type、data
	const {type,data} = action
	//根据type决定如何加工数据
	switch (type) {
		case 'increment': //如果是加
			return preState + data
		case 'decrement': //若果是减
			return preState - data
		default:
			return preState
	}
}

接下来创建store,store.js中的代码如下:

import {createStore} from 'redux'//引入createStore,专门用于创建redux中最为核心的store对象
import countReducer from './count_reducer'//引入为Count组件服务的reducer
//暴露store
//createStore中接收一个参数,这个参数就是一个reducer
export default createStore(countReducer)

以上,一个简单的redux就创建好了,接下来就在组件中写代码,点击按钮后,创建一个action对象传递给store,但是注意redux只负责操作,不负责渲染,他操作完了,数据改变了,但是数据并没有重新渲染,我们还需要手动重新渲染一下,在组件一创建时,就检测redux中状态的变化,只要变化,就调用render重新渲染、
组件的index.js中代码如下:

import React, { Component } from 'react'
import store from '../../redux/store'//引入store,用于获取redux中保存状态

export default class Count extends Component {
	state = {carName:'奔驰c63'}// 这是你组件自己用的数据

	/* componentDidMount(){
		//检测redux中状态的变化,只要变化,就调用render
		store.subscribe(()=>{// 订阅redux中状态的更改
			//更新组件自己的state
			//你一调setState,react就会帮你改状态,改完了他自动帮你调render重新渲染,但由于传入的是一个空对象,啥也不更新,所以骗了react一下
			this.setState({})
		})
	} */
	//但是如果你有3000个组件,每个组件里都要写上面的代码,冗余,所以我们在index.js中监听

	increment = ()=>{//加法
		const {value} = this.selectNumber
		//把action传给store
		store.dispatch({type:'increment',data:value*1})
	}
	decrement = ()=>{//减法
		const {value} = this.selectNumber
		store.dispatch({type:'decrement',data:value*1})
	}
	incrementIfOdd = ()=>{//奇数再加
		const {value} = this.selectNumber
		const count = store.getState()
		if(count % 2 !== 0){
			store.dispatch({type:'increment',data:value*1})
		}
	}
	incrementAsync = ()=>{//异步加
		const {value} = this.selectNumber
		setTimeout(()=>{
			store.dispatch({type:'increment',data:value*1})
		},500)
	}

	render() {
		return (
			<div>
				{/*store.getState()获取状态*/}
				<h1>当前求和为:{store.getState()}</h1>
				<select ref={c => this.selectNumber = c}>
					<option value="1">1</option>
					<option value="2">2</option>
					<option value="3">3</option>
				</select>&nbsp;
				<button onClick={this.increment}>+</button>&nbsp;
				<button onClick={this.decrement}>-</button>&nbsp;
				<button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>&nbsp;
				<button onClick={this.incrementAsync}>异步加</button>&nbsp;
			</div>
		)
	}
}

但是如果你有3000个组件,每个组件里都要写componentDidMount(){...},冗余,所以我们在index.js中监听,index.js中代码如下:

import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import store from './redux/store'

ReactDOM.render(<App/>,document.getElementById('root'))
//只要redux中的数据发生任何变化,我就更新整个<App/>,因为有diff算法,会找到变更的地方然后改变,局部渲染,这样并不会效率低
store.subscribe(()=>{
	ReactDOM.render(<App/>,document.getElementById('root'))
})

redux完整版

使用action creators创建action,count_action.js中的代码如下:

import {INCREMENT,DECREMENT} from './constant'
/*
 data:data精简为data
 (data)=>{return {type:INCREMENT,data}} 精简为:
 data => {type:INCREMENT,data} 这个{}会被认为是函数体那个{} 而不会认为你要返回一个对象
 下面这样写 在外层包一个()他就知道你返回的是个对象
 */
export const createIncrementAction = data => ({type:'increment',data})
export const createDecrementAction = data => ({type:'decrement',data})

在组件中,通过调用action中的方法创建action,组件的index.js中代码如下:

import React, { Component } from 'react'
import store from '../../redux/store'//引入store,用于获取redux中保存状态
import {createIncrementAction,createDecrementAction} from '../../redux/count_action'//引入actionCreator,专门用于创建action对象

export default class Count extends Component {
	state = {carName:'奔驰c63'}
	
	increment = ()=>{//加法
		const {value} = this.selectNumber
		store.dispatch(createIncrementAction(value*1))
	}
	decrement = ()=>{//减法
		const {value} = this.selectNumber
		store.dispatch(createDecrementAction(value*1))
	}
	incrementIfOdd = ()=>{//奇数再加
		const {value} = this.selectNumber
		const count = store.getState()
		if(count % 2 !== 0){
			store.dispatch(createIncrementAction(value*1))
		}
	}
	incrementAsync = ()=>{//异步加
		const {value} = this.selectNumber
		setTimeout(()=>{
			store.dispatch(createIncrementAction(value*1))
		},500)
	}

	render() {
		return (
			<div>
				<h1>当前求和为:{store.getState()}</h1>
				<select ref={c => this.selectNumber = c}>
					<option value="1">1</option>
					<option value="2">2</option>
					<option value="3">3</option>
				</select>&nbsp;
				<button onClick={this.increment}>+</button>&nbsp;
				<button onClick={this.decrement}>-</button>&nbsp;
				<button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>&nbsp;
				<button onClick={this.incrementAsync}>异步加</button>&nbsp;
			</div>
		)
	}
}

其余代码不变

异步action版

action分为异步action和同步action:
(1)action是一个对象,就被称为同步action,因为同步action中没有异步的任务。
(2)action是一个函数,就被称为异步action,因为只有函数能开启异步任务,数组、基本数据类型都不行。
前面使用的就是同步action,我们在实现异步加这个效果时,在组件里等了500ms后将action传给store,传递的是同步action,等500ms这个动作是在组件中进行的,之前的代码如下:

incrementAsync = ()=>{
	const {value} = this.selectNumber
	setTimeout(()=>{
		store.dispatch(createIncrementAction(value*1))
	},500)
}

现在我们希望等500ms这个动作是在action中进行的,就需要异步action来实现。想象一下,假如你现在去餐厅吃饭,你可以等5分钟再点菜,然后厨师做好了立马给你端上来,你也可以对厨师说,来碗蛋炒饭,5分钟之后再上。厨师做好了等5分钟给你端上来,一个就是自身等5分钟,一个是厨师等5分钟,我们之前就是组件自身等500ms然后通知store,使用同步action实现,现在我们希望action自己等500ms创建再传给store,这就需要用到异步action。上述代码改为以下,之前store.dispatch使用的是同步action方法,现在使用异步action方法

incrementAsync = ()=>{
	const {value} = this.selectNumber
	store.dispatch(createIncrementAsyncAction(value*1,500))
}

接下来在count_action.js中定义createIncrementAsyncAction方法。首先,createIncrementAsyncAction是一个函数,其次根据前面对异步action的定义知,异步action返回的action是一个函数,为什么是一个函数?因为只有函数能开启异步任务,所以在返回的这个函数里还需要开启异步任务,代码如下:

import store from './store'
export const createIncrementAsyncAction = (data,time) => {
	return ()=>{
		setTimeout(()=>{// 开启异步任务,异步action形成
			store.dispatch(createIncrementAction(data))
		},time)
	}
}

我们在index.jsx中写的代码:store.dispatch(createIncrementAsyncAction(value*1,500)),而createIncrementAsyncAction这个函数返回的是一个函数,也就是向store提交的是个函数,但是store他接收对象,如果我们想让store接收函数并执行该函数需要借助中间件redux-thunk,安装:yarn add redux-thunk,在store.js中使用中间件,让store可以接收一个函数并执行:
(1)从redux中引入applyMiddleware用于执行中间件
(2)引入redux-thunk,用于支持异步action
(3)applyMiddleware(thunk)执行中间件redux-thunk,并作为第二个参数传入createStore,用于支持异步action
store.js中具体代码如下:

//引入createStore,专门用于创建redux中最为核心的store对象。1. applyMiddleware用于执行中间件
import {createStore,applyMiddleware} from 'redux'
import countReducer from './count_reducer'//引入为Count组件服务的reducer
import thunk from 'redux-thunk'//2. 引入redux-thunk,用于支持异步action
//暴露store。3. applyMiddleware(thunk)执行中间件redux-thunk,并作为第二个参数传入createStore,用于支持异步action
export default createStore(countReducer,applyMiddleware(thunk))

回到count_action.js中,createIncrementAsyncAction返回的这个函数本身就是store帮你调用的,所以count_action.js不用引入store,也不用store.dispatch()进行分发,createIncrementAsyncAction返回的这个函数本身就是store帮你调用的,store在调用她的时候会将dispatch作为参数传入,所以count_action.js中createIncrementAsyncAction定义如下:

export const createIncrementAsyncAction = (data,time) => {
	return (dispatch)=>{
		setTimeout(()=>{// 开启异步任务,异步action形成
			dispatch(createIncrementAction(data))
		},time)
	}
}

异步action中一般都会调用同步action,异步action不是必须要用的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值