一、React 基础知识
官网:https://reactjs.org doc.react-china.org
Facebook开源,一个用来动态构建用户界面的js库
react特点:
Declarative(声明式编码)
Component-Based(组件化编码)
Learn Once, Write Anywhere(支持客户端与服务器渲染)
高效 单向数据流
React高效的原因:
虚拟(virtual)DOM, 不总是直接操作DOM
DOM Diff算法, 最小化页面重绘,相当于 ajax 的局部更新
学习React需要比较牢固的JS基础和熟悉ES6语法; React没有太多的api,可以说用react编程都是在写js。
1. react 搭建项目
1. 本地js库方式 搭建项目
// 1. 相关js库
/*
react.js React的核心库
react-dom.js 操作DOM的扩展库
babel.min.js
解析JSX语法代码转为纯JS语法代码的库
解决浏览器 不支持 es6 的问题 ,将es6 转化为 es5
下载到 本地 babel 库的用法:
<script src="../js/babel.min.js"></script>
<script type="text/babel"></script> */
// 2. 在页面中导入本地 相关 js 库
<script src="../js/react.js"></script>
<script src="../js/react-dom.js"></script>
<script src="../js/babel.min.js"></script>
// 3. 使用本地库时,实现 渲染 dom
<script type="text/babel"> // 否则babel 发挥不了作用
...
</script>
/*
1.创建虚拟 DOM 对象:虚拟DOM对象-可以直接在js里 写 HTML
let user = <h2>Hello React!!!</h2>;
2.渲染虚拟 DOM 对象:将虚拟对象转换为真的DOM对象,插入到页面的容器中
ReactDOM.render(虚拟DOM对象,页面中的容器 container)
console.log(ReactDOM); // Object
ReactDOM.render(user, document.getElementById("container"));
*/
2. webpack构建方式搭建项目
npm init -y
// 1. 安装包 npm i react react-dom -S
// react 这个包,是专门用来创建React组件、组件生命周期等这些东西的;
// react-dom 里面主要封装了和 DOM 操作相关的包,比如,要把 组件渲染到页面上
// 2. 在项目中导入两个相关的包
import React from 'react' // 使用JSX来描述UI,所以解耦了和DOM的操作
import ReactDOM from 'react-dom' // react-dom 做渲染层,去渲染实际的DOM
// 3. 想要使用 JSX 语法 , .babelrc
// 需要 npm i babel-preset-react --dev .babelrc 中添加 语法配置
// JSX语法的本质:还是以 React.createElement 的形式来实现的,并没有直接把 用户写的 HTML代码,渲染到页面上;
{
"presets": ["env", "stage-0", "react"],
"plugins": ["transform-runtime"]
}
3. 脚手架方式搭建项目
npm isntall -g create-react-app 或者 yarn add create-react-app -g
create-react-app xiaodi // 开始创建项目
npx create-react-app project01 // 创建项目 -可选方案
npm start 或 yarn start // 运行项目
创建项目的同时会帮你自动下载依赖,所以不用自己 npm install安装依赖了,创建完成后直接cd xiaodi去到当前项目,执行 npm start 或 yarn start 运行项目
2. 分析项目目录架构
xiaodi
|—— README.md 项目说明文档
|—— node_modules 依赖文件夹
|—— package.json npm依赖管理包
|—— .gitignore
|—— public 静态资源
| |—— favicon.ico
| |—— index.html
| |—— manifest.json
|—— src 源码
|—— App.css
|—— App.js
|—— App.test.js 测试
|—— index.css
|—— index.js 入口js
|—— logo.svg
|—— serviceWorker.js 对PWA的⽀持
3. 虚拟dom
1.创建虚拟DOM的2种方式
// 1. 纯JS(一般不用):
let element1 = React.createElement('h2',{id: 'box1', className: 'box2'}, 'React.createElement创建的虚拟DOM对象')
ReactDOM.render(element1, documment.getElementById('example1'));
// 2. JSX
let element2 = <h3>JSX创建的虚拟DOM对象</h3>;
ReactDOM.render(element2, documment.getElementById('example2'));
/* 技术点:
1). 使用JSX创建虚拟DOM
2). React能自动遍历显示数组中所有的元素
3). array.map()的使用 */
2.渲染虚拟DOM
虚拟DOM的本质:使用js对象 模拟dom树
虚拟dom的目的:为了实现 dom节点的高效更新
DOM和虚拟DOM的区别:
DOM是由浏览器中的JS提供功能,所以我们只能人为的使用 浏览器提供的固定的API来操作DOM对象;
虚拟DOM:并不是由浏览器提供的,而是我们程序员手动模拟实现的,类似于浏览器中的DOM,但是有着本质的区别;
渲染虚拟DOM:
1. 语法: ReactDOM.render(virtualDOM, containerDOM) :
2. 作用: 将虚拟DOM元素渲染到真实容器DOM中显示
3. 参数说明
* 参数一: 纯js或jsx创建的虚拟dom对象
* 参数二: 用来包含虚拟DOM元素的真实dom元素对象(一般是一个div)
3. 虚拟DOM diff 算法
// 目的:实现新旧dom的对比
// 作用:最小化 页面重绘 实例:倒计时效果
1. 虚拟DOM是什么?
一个虚拟DOM(元素)是一个一般的js对象, 准确的说是一个对象树(倒立的)
* 虚拟DOM保存了真实DOM的层次关系和一些基本属性,与真实DOM一一对应
* 如果只是更新虚拟DOM, 页面是不会重绘的
2. Virtual DOM 算法的基本步骤
初始化显示:
--->创建虚拟DOM(JS对象)树
--->真实DOM树
--->绘制界面显示
更新界面:
--->setState()更新状态
--->重新创建虚拟DOM树
--->对新树和旧树进行比较得到差异(dom diff)
--->更新变化的虚拟DOM对应的真实DOM(局部更新)
--->重绘局部界面
3. 进一步理解
Virtual DOM 本质上就是在 JS 和 DOM 之间做了一个缓存。
* 例子:
* CPU: JS
* 硬盘: 真实DOM
* 内存: 虚拟DOM
diff 算法
tree diff
新旧DOM树,逐层对比的方式,就叫做 tree diff,每当我们从前到后,把所有层的节点对比完后,必然能够找到那些 需要被更新的元素;
component diff
在对比每一层的时候,组件之间的对比,叫做 component diff;当对比组件的时候,如果两个组件的类型相同,则暂时认为这个组件不需要被更新,如果组件的类型不同,则立即将旧组件移除,新建一个组件,替换到被移除的位置;
element diff
在组件中,每个元素之间也要进行对比,那么,元素级别的对比,叫做 element diff;
key
key这个属性,可以把 页面上的 DOM节点 和 虚拟DOM中的对象,做一层关联关系;
4. jsx的实质
JSX语法即 js和html的混合体,实际的核心逻辑就是用 js去实现的,常见的代码如下:
// 作用: 用来创建虚拟DOM(元素)对象
// 注意:创建的虚拟DOM对象, 它不是字符串, 也不是HTML/XML标签,而是JS对象
// 在JSX创建DOM的时候,所有的节点,必须有唯一的根元素进行包裹
// 如果要写注释了,注释必须放到 {} 内部
var msg = "hello JSX!!!";
var ele = <h1>{msg}</h1>; // html 里 写js变量 必须加 大括号{}!!!
ReactDOM.render(<App/>,document.getElementById('root')); // (即React.createElement的调用)
babel.min.js的作用
* 浏览器的js引擎是不能直接解析JSX语法代码的, 需要babel转译为纯JS的代码才能运行
* 只要用了JSX,都要加上type="text/babel", 声明需要babel来处理
基本语法规则:
babel工具将 js转换为js :
1. 找html 的结束标签: 遇到 <开头的代码, 以标签的语法解析: html同名标签转换为html同名元素, 其它标签需要特别解析
2. 遇到以 { 开头的代码,以JS的语法解析: 标签中的js代码必须用{}包含
{}里的是 js的变量
插值表达式
1. 表达式: 合法js表达式即可 <h1>{name}</h1>
2. 函数也是表达式
<p>{formatName({ firstName: "tom", lastName: "cruis"})}</p>
3. jsx也是表达式 {<p>hello, jerry!</p>}
属性绑定
<img src={logo} style={{ width: "100px"}} alt="logo" className="img" /> // class 在ES6中是一个关键字
<label htmlFor="ff">fff</label> label标签的for属性需要替换为htmlFor
5. 单向数据流
单向数据流
react中 单向数据流 不能进行数据双向绑定,意味着
任何用户的输入 都必须实现值的绑定以及事件的输出,输入和输出都需要手动写
即 给 一个值 加上事件监听
例:Input 框 输入 和 输出 分别管理:
<input type="text"
value={this.state.name} // 在state状态中定义初始值
onChange={ e => this.handleChange(e)}
/>
// 监听input 值的改变,更新状态
handleChange = (e) => {
this.setState({name: e.target.value})
}
事件处理函数 三种写法
//1.写法错误, this.handleChange(e) 表示立即执行一次,在input还没发生改变时就执行了
onChange={this.handleChange(e)}
// 2. 给事件声明一个函数名称,当事件触发后,处理函数 用箭头函数写法 改变 this 的指向
onChange={this.handleChange}
// 3.带有参数 的 处理函数写法
onChange={ e => this.handleChange(e)}
组件化的实现思想
react 的开发模式 UI = f (state) -- 把当前的状态进行 函数的计算 得到 UI
6. state属性
初始化状态
// 1. 在constructor() 函数中 this.state 写
constructor (props) {
super(props)
this.state = {
stateProp1 : value1,
stateProp2 : value2
} }
// 2. 直接在组件内部 以变量的形式写
class Home extends Components{
state = { isShow: false }
}
// 3. 读取某个状态值 this.state.属性名
// 组件被称为"状态机":通过更新组件的状态值来更新对应的页面显示(重新渲染)
更新状态setState
// 1. 对象形式 更新
this.setState({stateProp1 : value1,stateProp2 : value2})
// 2. 函数形式 更新
1.在组件里面我们通过{}在JSX里面渲染变量
2.如果数据需要修改,并且需要页面同时响应改变,那就需要把变量放在state里面,同时使用setState修改
3.初始化状态state
this.state = { count: 0 };// 初始化状态
更新状态使用 setState,不能直接 this.state.count=xxx
this.setState({ count: this.state.count + 1}); // 更新状态
setState注意事项
setState是异步的,底层设计同一个生命周期会批量操作更新state;
setState第二个参数是一个可选参数,传入一个回调函数可以获取到最新的state;
当修改的state依赖上一次修改的state的值时,可使用以下这种方法修改:
this.setState((prevState, prevProps)=>({
//prevState:上一次修改的状态state
//prevProps:上一次修改的属性props
count: prevState.count + 1
}), () => {
console.log(this.state.count); //这里可以获取到最新的state
});
props和state属性的区别
* props: 从组件外部向组件内部传递数据, 组件内部只读不修改
* state: 组件内部变化的数据
实例:自定义组件
/* 功能说明如下:
1. 显示h2标题, 初始文本为: 你喜欢我
2. 点击标题更新为: 我喜欢你 */
class LikeMe extends React.Component {
constructor(props) {
super(props)
this.state = { isLikeMe: false }
this.switchLikeMe = this.switchLikeMe.bind(this)
}
switchLikeMe() {
let isLikeMe = !this.state.isLikeMe;
this.setState({isLikeMe})
}
render() {
let text = this.state.isLikeMe ? '你喜欢我' : '我喜欢你'
return <h2 onClick={this.switchLikeMe}>{text}</h2>
}
}
ReactDOM.render(<LikeMe/>, document.getElementById('example'))
7. props属性
初识props
1. 每个组件对象(实例对象)都会有props(properties的简写)属性
2. 组件标签的所有属性都保存在props中
3. 内部读取某个属性值: this.props.propertyName
props 属性中的值 都是只读,不能更改
props 的作用
// 通过标签属性从组件外 向组件内传递数据(只读)
class Person extends React.Component{
render(){
console.log(this.props); // object {username: "Amy", sex:"女",age:18}
return (
<ul>
<li>姓名:{this.props.username}</li>
<li>性别:{this.props.sex}</li>
<li>年龄:{this.props.age}</li>
</ul>
) }};
let person = {username: "Amy", sex:"女",age:18}; // 初始化数据
// 方式1:分别传入属性和相对应的值
ReactDOM.render(<Person name={person.name} age={person.age} sex={person.sex}/>, document.getElementById('example1'));
// 方式2:对象方式 传入属性和值 扩展属性: 将对象的所有属性通过props传递
ReactDOM.render(<Person {...person}/>,document.getElementById("example1"));
// props 实现父子组件通信
// 父组件向子组件传递属性,子组件利用 props 接收,使用例子如下:
<PropsDemo title="Tim老师教react"></PropsDemo> // 父组件传入
<h1>{this.props.title}</h1> //class子组件接收使用
function xxx(props){ return <h1>{props.title}</h1> } //函数型子组件接收使用
function xxx({title}){return <h1>{title}</h1>} //解构赋值写法
props 给 state传递数据
this.state = {
msg: 'ok',
count: props.initcount // 注意:在 constructor 构造函数中 ,不能写 this.props
} }
props默认值
/* 在封装一个组件时,组件内部,肯定有一些数据的必须的,哪怕用户没有传递一些相关的启动参数,这时候组件内部尽量给自己提供一个默认值。
在React 中,使用静态的 defaultProps属性,来设置组件的默认属性值
static defaultProps={initcount:0} 如果外界没有传递initcount,那么,自己初始化一个数值为0
/* 需求: 自定义用来显示一个人员信息的组件, 效果如页面. 说明
1). 如果性别没有指定, 默认为男 --> ES6形参默认值
2). 如果年龄没有指定, 默认为18 */
// 设置组件的默认props 属性值
Person.defaultProps = {name: "amy", sex: "男", age: 18 }
prop-types 的作用
// 使用 prop-types库 对 props属性 进行类型校验
// 规范和定义 由父组件传递过来的 props属性的必要性和数据类型
import PropTypes from "prop-types" ; // 需要引入库 ,才能使用 PropTypes这个对象
static propTypes = {
initcount: React.PropTypes.number.isRequired, // 必须传数据 并且是 指定的数据类型
}
props.children
合法的js 表达式
可以是 jsx : 一个js 对象
<div> <p>感谢使用</p> </div>
也可以是 函数:
{c => ( <div><h1>欢迎光临</h1><p>感谢使用</p></div> )}
8. refs 属性
// 原生js 想要获取一个标签内的内容:
<h3 id="myh3">当前的数量是:{this.state.count}</h3>
document.getElementById('myh3').innerHTML
// react 的 ref 用法:
<h3 id="myh3" ref="h3">当前的数量是:{this.state.count}</h3>
console.log(this.refs.h3.innerHTML)
// 典型应用:父组件调用子组件的方法
// 1. 组件内的标签都可以定义ref属性来标识自己
// 2. 在组件中可以通过this.refs.refName来得到对应的真实DOM对象
// 3. 作用: 用于操作指定的ref属性的dom元素对象(表单标签居多)
* <input ref='username' />
* this.refs.username //返回input对象
条件渲染与数据循环
条件渲染写法,一般使用三元表达式
{this.state.showTitle?<h1>{this.props.title}</h1>:null}
//优化上面三元表达式写法,先在render函数里面定义一个变量装载结果
let result=this.state.showTitle?<h1>{this.props.title}</h1>:null
{result}
//直接使用 if else写
let result
if(this.state.showTitle){
result=( <h1>this.props.title</h1>)
}else{
result=null
}
{result}
数据循环渲染写法
class App extends React.Component{
constructor(props){
super(props);
this.state = {
goods: [
{ title: 'html+css基础⼊⻔', price: 19.8},
{ title: 'js零基础阶级', price: 29.8},
{ title: 'vue基础入门', price: 19.8},
{ title: 'vue电商单页面项目实战', price:39.8},
{ title: 'react零基础进阶单页面项目实战',price: 59.8},
]}}
render(){
return <div>
<ul>
{this.state.goods.map(good=>{
return <li key={good.title}>
<span>{good.title} </span>
<span>{good.price}元</span>
</li> })}
</ul>
</div>
} }
事件监听
以点击事件为例子,使用方法如下:
//小驼峰写法,事件名用{}包裹
<button onClick={}></button>
由于react的this指向问题,所以在事件绑定时要特别注意,否则会出现bug 一般使用的事件绑定写法有三种:
- 第一种利用bind绑定,写法如下,这种比较少用
//在constructor里面利用bind 绑定继承this,解决方法的this指向问题
constructor(props) {
super(props);
this.showTitleFun = this.showTitleFun.bind(this);
}
//执行某些操作
showTitleFun(){ this.setState({})}
//当点击时 执行函数
<button onClick={this.showTitleFun}>不显示title</button>
- 推荐:第二种箭头函数写法
// 执行某些操作
showTitleFun = () => { this.setState({});};
//在DOM元素上直接使用
<button onClick={this.showTitleFun}>不显示title</button>
- 第三种:直接使用箭头函数返回一个函数
showTitle = () => { this.setState({});};
<button onClick={() => this.showTitleFun()}>不显示title</button>
样式的编写
<img style={{ width: "100px",height:"100px" }} /> // 1.行内样式写法
<img className="img" /> // 2.添加类名
<img src={logo} /> // 3. 添加属性
React实现双向数据绑定
React实现input的双向数据绑定要点:
- 动态绑定value属性
//在state中定义一个变量绑定input的value属性
this.state={ inputval:'我是input的初始值'}
//然后在input上动态绑定上面state定义的变量
<input type="text" value={this.state.inputval}/>
- 监听input的onChange事件
//定义onChange绑定的事件
inputvalChange = e => {
this.setState({
inputval: e.target.value
});
}
//在input上绑定绑定inputvalChange到onChange上
<input type="text"
value={this.state.inputval}
onChange={e => this.inputvalChange(e)}
/>
二、组件生命周期
1. 性能优化之PureComponent
-
PureComponent是内部定制了shouldComponentUpdate生命周期的Component
-
它重写了一个方法来替换 shouldComponentUpdate 生命周期方法
-
平常开发过程中设计组件能使用 PureComponent的地方都尽量使用
-
想要使用 PureComponent 特性要记住两个小原则:
- 确保数据类型是值类型
- 如果是引用类型,确保地址不变,同时不应当有深层次数据变化
-
使用 PureComponent可以省去shouldComponentUpdate生命周期的代码,代码会简单很多
2.性能优化之React.memo
React.memo是一个高阶组件的写法
React.memo让函数组件也拥有了PureComponent的功能
使用例子如下:
const MemoComponent = React.memo((props) => {
return (
<div>
<p>{props.xxx}</p>
<p>{props.xxx}</p>
</div>
);
});
三、React 组件的写法
1.傻瓜组件和聪明组件的区别
-
傻瓜组件也叫展示组件,负责根据props显示页面信息;
-
聪明组件也叫容器组件,聪明组件也叫容器组件。
-
分清楚展示组件和容器组件的优势:
- 分离工作组件和展示组件;
- 提高组件的重用性;
- 提高组件可用性和代码阅读;
- 便于测试于后续的维护。
2.函数式组件
-
函数式组件是一种无状态组件,是为了创建纯展示组件,这种组件只负责根据传入的props来展示,不涉及到state状态的操作;
-
组件不会被实例化,整体渲染性能得到提升
-
组件不能访问this对象;
-
组件无法访问生命周期的方法;
-
无状态组件只能访问输入的props,同样的props会得到同样的渲染结果,不会有副作用
具体写法如下:
//这种写法是新建一个组件页面,把组件暴露出去的写法
import React from 'react'
export default function xxx() {
return ( <div>我是函数式组件</div>)}
// 这种写法是在页面内部创建组件不用给外部使用,只供页面内部使用
function xxx() {
return (<div> 我是函数式组件</div>)}
3.class 组件
-
React.createClass是react刚开始推荐的创建组件的方式,现在基本不会用到了;
-
React.Component是以ES6的形式来创建react的组件的,是React目前极为推荐的创建有状态组件的方式;
- 在里面我们可以写入我们的状态、生命周期、构造函数等
- 具体使用如下所示:
import React, { Component } from 'react'
export default class ConditionLoop extends Component {
render() {
return ( <div> </div>
) } }
4.组件复合写法
组件复合类似于我们在vue框架里面用的组件插槽
具体使用方式如下:
function WelcomeXdDialog() {
const confirmBtn=(
<button onClick={() => alert("React真好玩")}>确定</button>
);
return (
<XdDialog color="green" footer={confirmBtn}>
<h1>欢迎来到小D课堂</h1>
<p>欢迎您来到小D课堂学习react!!!</p>
</XdDialog>
); }
// XdDialog
function XdDialog(props) {
return (
<div style={{ border: `4px solid ${props.color|| "blue"}` }}>
{/* 等同于vue中匿名插槽*/}
{props.children}
{/* 等同于vue中具名插槽*/}
<div className="abc">{props.footer}</div>
</div>
); }
5.高阶组件HOC
- 高阶组件—HOC(Higher-Order Components)
- 高阶组件是为了提高组件的复用率而出现的,抽离出具有相同逻辑或相同展示的组件
- 高阶组件其实是一个函数,接收一个组件,然后返回一个新的组件,返回的这个新的组件可以对属性进行包装,也可以重写部分生命周期
- 例子如下:
// 创建withLearnReact高阶组件,传递一个组件进去,返回一个新的组件NewComponent
const withLearnReact= (Component) => {
const NewComponent= (props) =>{
return <Component {...props} name="欢迎大家来到**课堂学习React"/>
}
return NewComponent
}
6.高阶组件的链式调用
需求如下:
- 编写一个高阶组件进行属性的添加
- 编写一个高阶组件编写生命周期
- 然后将以上两个高阶组件进行链式调用
例子如下:
import React, { Component } from 'react'
// 编写第一个高阶组件。传递一个组件进去,返回一个新的组件 (返回的是函数组件)
const withLearnReact=(Comp)=>{
const NewComponent=(props)=>{
return <Comp {...props} name="欢迎大家来到小D课堂学习React"></Comp>
}
return NewComponent
}
// 编写第二个高阶组件,重写生命周期,注意,重写生命周期需要class组件(返回的是class组件)
const withLifeCycle= Comp => {
class NewComponent extends Component{
//重写组件的生命周期
componentDidMount(){ console.log('重写componentDidMount生命周期')}
render(){
return <Comp {...this.props}></Comp>
}
}
return NewComponent
}
class HOC extends Component {
render() {
return (
<div><h1>欢迎⼤家体验⾼阶组件的写法</h1>
{this.props.title}
<p>姓名:{this.props.name}</p>
</div>
) } }
export default withLifeCycle(withLearnReact(HOC))
7 高阶组件装饰器写法
由于高阶组件链式调用的写法看起来比较的麻烦也不好理解。逻辑会看的比较绕
ES7中就出现了装饰器的语法,专门拿来处理这种问题的
- 安装支持装饰器语法的babel编译插件:
npm install --save-dev @babel/plugin-proposal-decorators
- 更改配置文件代码:
module.exports = override(
// 支持装饰器
addBabelPlugins([
'@babel/plugin-proposaldecorators',{ legacy: true}
] ),
- 使用方式:@高阶组件名称
高阶组件的声明要放在使用组件前面
8.组件通信之 上下文Context
- 上下文context有两个角色:Provider 数据提供,Consumer 数据读取
- 使用context,避免了中间props元素的传递的写法
- context的设计目的是为了共享对于一个组件树而言是“全局”的数据
import React, { Component } from "react";
// 1.创建上下文
const XdContext=React.createContext();
const {Provider,Consumer}=XdContext;
// 创建一个传递的数据源
let store={ name:"我是Tim1",from:"我来自小D课堂" };
class Info extends Component{
render(){
return (
<Consumer>
{ store => { return (
<div><p>姓名:{store.name}</p><p>出处:{store.from}</p></div>)}}
</Consumer>
) } }
function ToolBar(props){
return ( <div><Info></Info></div> )}
export default class Context1 extends Component{
render(){
return (
<Provider value={store}>
<ToolBar></ToolBar>
</Provider>
) } }
四、React函数式编程之Hook
React Hooks解决了什么问题?
-
函数式组件不能使用 state,React Hooks让我们更好的拥抱函数式编程,让函数式组件也 能使用state功能,因为函数式组件比 class组件更简洁好用
-
副作用问题:数据获取、订阅、定时执行任务、手动修改ReactDOM这些行为一般称为副作用
(可以使用 useEffect来处理)
-
有状态的逻辑重用组件
-
复杂的状态管理:
- 之前我们使用 redux、dva、mobx第三方状态管理器来进行复杂的状态管理
- 现在我们可以使用 useReducer、useContext配合使用实现复杂状态管理,不用再依赖第三方状态管理器
-
开发效率和质量问题:函数式组件比class组件简洁,开发的体验更好,效率更高,同时应用的性能也更好
1. useState 组件状态管理
基本使用如下所示:
- state是要设置的状态变量
- setState是更新state的方法,只是一个方法名,可以随意更改
- initState是初始的state,可以是随意的数据类型,也可以是回调函数,但是函数必须是有返回值
const [state,setState]=useState(initState);
import React,{useState} from 'react'
const App = () => {
const [count,setCount]=useState(0)
return (
<div>
<h1>我是函数式组件,这是React Hook的第一个钩子函数</h1>
<div>你点击了{count}次</div>
<button onClick={()=>setCount(count+1)}>点击</button>
</div>
) }
export default App;
2.useEffect 副作用处理钩子
useEffect也是componentDidMount组件完成挂载、componentDidUpdate组件已经更新和componentWillUnmount组件即将销毁 这3个生命周期方法的统一
-
基本使用如下所示:
-
callback: 回调函数,作用是处理副作用逻辑,callback可以返回一个函数,用作清理
-
array(可选参数):数组,用于控制useEffect的执行,分三种情况:
- 空数组,则只会执行一次(即初次渲染render),相当于 componentDidMount
- 非空数组,useEffect会在数组发生改变后执行
- 不填array这个数组,useEffect每次渲染都会执行
-
useEffect(callback,array)
import {useState,useEffect} from 'react';
const App=() => {
const [count,setState]=useState(0);
//更新页面标题
useEffect(() =>{ document.title=`您点击了${count}次了哦`},[count])
return (
<div>
<div>你点击了{count}次</div>
<button onClick={()=>setState(count+1)}>点击</button>
</div>
) }
3. useContext 全局数据共享
- 它接受React.createContext()的返回值作为参数,即context对象,并返回最近的context
- 使用 useContext是不需要再使用 Provider 和 Consumer 的
- 当最近的context更新时,那么使用 该context的hook将会重新渲染
基本使用如下:
import { useContext} from 'react'; // 1. 引入hook
const Context=React.createContext({age:'18',name:'jerry'}); // 2.创建共享数据对象
const AgeComp= () =>{
const ctx = useContext(Context) // 3.接收 useContext()返回的数据对象
return(
// 4. 使用对象
<div>年龄:{ctx.age}岁</div>
) }
4. useReducer 复杂状态管理
useReducer是useState的一个增强体,可以用于处理复杂的状态管理
基本使用如下:
import { useReducer} from 'react'; // 引入 hook
[state,dispatch]=useReducer(reducer,initState,initAction) // 语法
-
useReducer的参数介绍
- reducer是一个函数,根据action状态处理并更新state
- initState是初始化的state
- initAction是useReducer初次执行时被处理的action,可不写
-
返回值 state,dispatch介绍
- state状态值
- dispatch是更新state的方法,他接受action作为参数
-
useReducer只需要调用 dispatch(action)方法传入action即可更新state
import React,{useReducer} from 'react'; // 1. 引入hook
const initState={count:0} //2. 初始state
// 3. 创建 reducer函数
const reducer = (state, action) => {
//3.1 根据action的类型去更新state
switch(action.type){
case 'reset': //当type是reset时,重置state的值回到初始化时候的值
return initState;
case 'add': //当type的值是add时,让count+1
return {count:state.count+1}; // 返回新的count:旧的state.count+1
case 'reduce': //当type的值是reduce时,让count-1
return {count:state.count-1};
default: //当type不属于上面的任意⼀一个值,state不做更改,直接放回当前state
return state
}}
export default function UseReducerComp(){
// 4. 声明并使用 hook
const [state,dispatch]=useReducer(reducer,initState);
return ( <div>
<p>当前数量是:{state.count}</p>
// 5. 通过调用 dispatch(action) 更新状态
<div><button onClick={()=>dispatch({type:'reset'})}>重置</button></div>
<div><button onClick={()=>dispatch({type:'add'})}>加一</button></div>
<div><button onClick={()=>dispatch({type:'reduce'})}>减一</button></div>
</div> )}
彩蛋:结合useContext会有无限种可能:useContext可以解决组件间的数据共享问题,而useReducer可以解决复杂状态管理的问题,如果把他们结合起来使用,就可以实现redux的功能了,意味着未来我们可以不再依赖第三方状态管理器了
5.useMemo 性能优化01
- useMemo 用于性能优化,通过记忆值来避免在每个渲染上执行高开销的计算
适用于复杂的计算场景,例如复杂的列表渲染,对象深拷贝等场景
import {useMemo} from "react";// 1. 引入钩子
const memoValue =useMemo(callback,array); // 2. 存入callback和数组给useMemo
// 需求:对下列两个对象进行合并
const obj1={taller:'180',name:'tom',age:'15'};
const obj2={taller:'170',name:'jerry',age:'18',sex:'女'};
const memoValue=useMemo(()=>Object.assign(obj1,obj2),[obj1,obj2])
// 使用方式:使用useMemo返回值
<div>姓名:{memoValue.name}---年龄:{memoValue.age}</div>
- callback是一个函数,用于处理逻辑
- array 控制useMemo重新执行的数组,array改变时才会重新执行useMemo
- useMemo的返回值是一个记忆值,是callback的返回值
- 注意:不能再useMemo里写副作用逻辑。。。
6.useCallback 性能优化02
返回值是callback本身。
UseCallback和useMemo一样,也是用于性能优化的,使用方法如下:
// 基本使用方法
const memoCallback= useCallback(callback,array);
const obj1={taller:'180',name:'tom',age:'15'}
const obj2={taller:'170',name:'jerry',age:'18',sex:'女'}
const memoCallback=useCallback(()=>Object.assign(obj1,obj2),[obj1,obj2])
// 使用:调用函数的执行+返回值对象的属性值
<div>姓名:{memoCallback().name}---年龄:{memoCallback().age}</div>
- callback是一个函数,用于处理逻辑
- array 控制useCallback重新执行的数组,array改变时才会重新执行useCallback
- 跟useMemo不一样的是返回值是callback本身,而useMemo返回的是callback函数的返回值
7. useRef 访问/操作dom
通常用来获取表单元素的value值
import react, { useRef } from "react"; // 1. 引入钩子
export default const UseRefComp=()=>{
const inputRef=useRef(); // 2. 使用钩子
// 定义获取input值的函数
const getValue= () => {console.log(inputRef.current.value)};//4.获取dom的current属性.value
return (
<div>
<input ref={inputRef} type="text"> // 3. 通过ref 属性绑定 钩子的返回值
<button onClick={getValue}>获取input的值</button>
</div>
) }
8.自定义 钩子函数
简单封装一个改变页面标题的 自定义Hooks
import React,{useEffect} from 'react';
// 封装的Hooks以use开头
const useChangeTitle= (title) =>{
useEffect(()=>{document.title=title},[title]) // 使用 useEffect 钩子
}
exprot default (props)=>{
useChangeTitle("自定义修改标题Hooks");
return (
<div>测试自定义Hooks</div>
)}
9. 使用React Hooks 的规则
-
只在组件的顶层调用Hooks
- Hooks的调用尽量只在顶层作用域进行调用
- 不要在循环,条件或嵌套函数中调用hook,否则可能会无法确保每次组件渲染时都以相同的顺序调用hook
-
只在函数组件调用Hooks
- React Hooks 目前只支持函数组件
-
React Hooks 的应用场景:
- 函数组件
- 自定义hooks
五、状态管理器 redux
5.1 redux 核心成员
redux 架构的设计核心:严格的单向数据流
问题:每次state更新,都会重新render,大型应用中会造成不必要的重复渲染。(react-redux解决重复渲染问题!!)
redux数据流走向:
// 1.store:整个应用的数据存储仓库,接收reducer作为参数(多个reducer时 参数是数组)
// 会将之前的state和action 传给 reducer
// 2.reducer(有state action 2个参数)
// 是一个函数, 根据action.type 修改/更新state
// 3.组件:获取state, 触发修改state的action
store.getState()获取初始state
store.dispatch({type:'add'}) // dispatch中的对象即是action
// actions (常量:函数名称)
// 描述操作的对象,组件调用dispatch时需要传入此对象,触发reducer函数
注意:redux 是一个单独的数据流框架,跟react没有直接联系,可在Jquery,在其他复杂项目里使用redux进行数据管理,简单项目只需要 state+props+context就够了
2. 用redux 编写计数器
npm install redux --save // 安装 redux
使用redux的步骤:
1. 从redux引入createStore用来创建数据仓库store,createStore是一个函数,需要传入reducer作为参数,返回值是我们需要的store
2. 在使用页面引入数据仓库store,
a. 组件页面: 通过getState()方法可以获取到数据仓库里的状态数据state,
通过dispatch(action)可以触发更改reducer函数
b. 入口文件:每次触发dispatch都会触发store.subscribe()方法,用来重新触发页面渲染
// 1.store.js
import { createStore } from 'redux';
// 2.创建 reducer函数(修改state)
const Reducer = (state=0,action ) => {
switch(action.type){
case 'add' : //当传入action的type为add的时候给state+1
return state+1;
case 'reduce' : //当传入action的type为reduce的时候给state-1
return state-1;
default: //当传入的都不是以上的类型是返回旧的state
return state;
} }
const store=createStore(Reducer); // 1. 创建stroe,有state和reducer的store
export default store; // 导出store
export default createStore(counter); //简写:创建store并导出
// 2. FirstRedux.js
import React, { Component } from 'react';
import store from './store';
export default class FirstRedux extends Component {
render() {
return (
<div>
<h1>尝试使用redux编写累加器</h1>
{/* 通过getState方法获取数据仓库里面的状态数据state */}
{store.getState()}
<div>
{/* 通过store.dispatch()触发reducer,修改state */}
<button onClick={()=>store.dispatch({type:'add'})}>加一</button>
<button onClick={()=>store.dispatch({type:'reduce'})}>减一</button>
</div>
</div>
)}}
// 3.index.js
import React from 'react'
import ReactDOM from 'react-dom'
import FirstRedux from './TryRedux/FirstRedux'
import store from './TryRedux/store'
const render=()=>{
ReactDOM.render(<FirstRedux/>,document.getElementById('root'))
}
render(); // 默认渲染一次
store.subscribe(render); // 订阅并监听store中state的改变
3. react-redux 改造计数器
由于redux的写法太繁琐,还每次都需要重新调用render,不太符合我们了解react编程,使用 connect 高阶组件装饰器模式进行简化封装代码
npm i react-redux -S // 安装 react-redux
// React-redux提供了两个api供我们使用:
<Provider store={store}/> 顶级组件,使得store中的数据 方法都应用到所有组件实例中, 当组件中修改state不会重新render
connect(mapstateToProps, mapDispatchToProps)(子组件)
高阶组件将组件连接到store的功能,功能为提供数据和方法, 用装饰器会使我们的代码看起来更洁易懂
1.index.js 入口js 改造代码 store.js暂时不用作改变
import React from 'react'
import ReactDOM from 'react-dom'
import FirstRedux from './TryRedux/FirstRedux' // 1. 引入应用的根组件
import store from './store/store' // 2. 引入store
import { Provider } from 'react-redux' // 3. 引入 provider 顶级组件
ReactDOM.render(
// 4. 顶级组件,绑定自定义的store属性传入store,所有组件都可以访问store中的数据了
<Provider store={store}>
<FirstRedux ></FirstRedux>
</Provider> ,
document.getElementById('root'))
// 订阅不需要了
// store.subscribe(render);
2.FirstRedux.js 应用根组件
import React, { Component } from 'react';
import { connect } from 'react-redux'; // 1. 引入connect 高阶组件
// 1. 第一种用法:mapStateToProp + mapDispatchToProps转换成props的属性,
// mapStateToProps 返回对象,把数据转换成props的属性
// mapDispatch 返回dispatch 方法的方法,把dispatch转换成props的方法
const mapStateToProps = (state) => { count: state };
const mapDispatchToProps = (dispatch) => {
return {
add:()=> dispacth({type:"add"}),
reduce: ()=> dispatch({type:"reduce"})
}
}
// 或:@connect(mapStateToProps,mapDispatchToProps)
class FirstRedux extends Component {
render() {
return ( <div>
<h1>尝试只用redux编写一个累加器</h1>
// 通过this.props.count 获取数据
{this.props.count}
<div>
// 调用this.props.add()方法更新state
<button onClick={()=>this.props.add()}>加一</button>
<button onClick={()=>this.props.reduce()}>减一</button>
</div>
</div>
export default connect(mapStateToProps, mapDispatchToProps)(FirstRedux); // 包装组件
// 或:export default FirstRedux
// 第二种高级用法:用装饰器方式,让组件拥有 store中的所有数据和方法
// 见4
4. 组件装饰器模式 简化 connect()()高阶组件
firstRedux.js
import { connect } from "react-redux"; // 1. 引入connect 高阶组件
// 第二种高级用法:用装饰器方式,让组件拥有store中的所有数据和方法
@connect(
state=>({count:state}),
//dispatch=>({
// add:()=>dispatch({type:'add'}),
// reduce:()=>dispatch({type:'reduce'})
// })
// 因为redux默认只支持同步写法,所以上面返回的dispatch的方法可以简写成以下代码:
{
add: ()=>({type:"add"}),
reduce: ()=>({type:"reduce"})
}
)
class FirstRedux extends Component {
render() {
return ( <div>
<h1>尝试只用redux编写一个累加器</h1>
{this.props.count}
<div>
<button onClick={()=>this.props.add()}>加一</button>
<button onClick={()=>this.props.reduce()}>减一</button>
</div>
</div>
) } }
export default FirstRedux;
注意:装饰器写法 需要配置 src/config-overrides.js
const {
override,
fixBabelImports, //按需加载配置函数
addBabelPlugins //babel插件配置函数
} = require('customize-cra');
module.exports = override(
fixBabelImports('import', {
libraryName: 'antd',
libraryDirectory: 'es',
style: 'css',
}),
addBabelPlugins( // 支持装饰器
[
'@babel/plugin-proposal-decorators',
{
legacy: true
}
]
)
);
5.redux 中间件 logger thunk
由于redux reducer默认只支持同步,实现异步任务或者延时任务时,我们就要借助中间件的支持
- redux-thunk 支持 reducer在异步操作结束后自动执行
npm i redux-thunk --save // 重点
- redux-logger 打印日志记录协助本地调试
npm i redux-logger --save
使用中间件例子: store.js
import { createStore,applyMiddleware, combineReducers } from 'redux';
// applyMiddleware应用型中间件 combineReducers进行复合实现状态的模块化
import logger from 'redux-logger';
import thunk from 'redux-thunk';
const fitstReducer = (state=0,action ) => {
console.log(action);
switch(action.type){
case 'add' :
return state+1;
case 'reduce' :
return state-1;
default:
return state;
} }
//有一个注意点就是logger最好放在最后,日志最后输出才不会出bug,因为中间件时按顺序执行
const store=createStore(fitstReducer,applyMiddleware(thunk,logger)); // 当触发dispatch时会打印日志
export default store;
First.js
import { connect } from "react-redux"; // 1. 引入connect 高阶组件
// 第二种高级用法:用装饰器方式,让组件拥有store中的所有数据和方法
@connect(
state=>({count:state}),
{
add: ()=>({type:"add"}),
reduce: ()=>({type:"reduce"}),
asycAdd: ()=>dispatch=>{
setTimeOut((
dispatch({type:"add"}
)=>{}, 2000)
)
}
}
)
class FirstRedux extends Component {
render() {
return ( <div>
<h1>尝试只用redux编写一个累加器</h1>
{this.props.count}
<div>
<button onClick={()=>this.props.add()}>加一</button>
<button onClick={()=>this.props.reduce()}>减一</button>
<button onClick={()=>this.props.asycAdd()}>2秒后加一</button>
</div>
</div>
) } }
export default FirstRedux;
6.combineReducers的用法
// store.js
import { createStore, combineReducers } from 'redux'; // 进行复合,实现状态的模块化
const store=createStore(combineReducers({counter:counter,user:user}),
applyMiddleware(thunk,logger));
**components.js
const mapStateToProps = state => {
return {
num: state.counter // 获取对应的reducer状态数据
user: state.user
}
}
7.抽离 reducer 和 action
1.新建store/reducer.js 存放 reducer 和 action
const fitstReducer = (state=0,action ) => {
console.log(action);
switch(action.type){
case 'add' :
return state+1;
case 'reduce' :
return state-1;
default:
return state;
}}
const add=()=>({type:'add'});
const reduce=()=>({type:'reduce'});
const asyncAdd=()=> dispatch =>{
setTimeout(()=>{dispatch({type:'add'})
},1000)};
export {fitstReducer, add,reduce,asyncAdd};
2.index.js 引入 reducer
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore ,applyMiddleware} from 'redux;
import thunk from 'redux-thunk';
import logger from 'redux-logger';
import {fitstReducer} from '../store/reducers.js';
const store=createStore(fitstReducer,applyMiddleware(thunk,logger));
ReactDOM.render(
<Provider store={store}>
<FirstRedux ></FirstRedux>
</Provider> ,
document.getElementById('root'))
3.FirstRedux.js 组件文件 引入 action
import React, { Component } from 'react'
import { connect } from 'react-redux'
import {add,reduce,asyncAdd} from '../store/reducer.js'
@connect( state=>({count:state}), {add,reduce,asyncAdd})
class FirstRedux extends Component {
render() {
return ( <div>
<h1>尝试使用redux编写一个累加器</h1>
{this.props.count}
<div>
<button onClick={()=>this.props.add()}>加1</button>
<button onClick={()=>this.props.reduce()}>减1</button>
<button onClick={()=>this.props.asyncAdd()}>延时加1</button>
</div>
</div> )}}
export default FirstRedux
Mobx 入门(类似轻量级redux)
React 和MobX 是一对强力组合
React是一个消费者,将应用状态state渲染成组件树对其渲染
Mobx是一个提供者,用于存储和更新状态state
npm i mobx mobx-react -S // 下载
// 1. 新建store/mobx.js
import{ observable,action,computed} from "mobx";
// 创建观察者
const appState = observable({ num: 0 }) // 声明state
// 声明观察者的方法
appState.increment = action(()=>{ appState.num+=1;}) // 修改state
appState.decrement = action(()=>{ appState.num-=1;})
export default appState;
// 2. 给子组件传递状态及方法
import appState from './store/mobx'
ReactDOM.render((
<div> <MobxTest appState= {appState}/> </div>
), document.querySelector('#root'));
// 3. MobxTest.js 中获取state,调用方法
import{ observer} from "mobx-react";
class MobxTest extends Component{
render() {
return(
<div>
{this.props.appState.num}
<button onClick={() =>this.props.appState.decrement()}>-1</button>
<button onClick={() =>this.props.appState.increment()}>+1</button>
</div>
);}}
export default observer(MobxTest);
对比redux和Mobx:
使用Mobx入门简单,构建应用迅速,但是当项目足够大的时候,还是redux,那还是开启严格模式,再加上一套
状态管理的规范。
六、路由 react-router-dom
特点:
- 秉承react一切皆组件,路由也是组件
- 分布式的配置,分布在每个⻆落
- 包含式配置,可匹配多个路由
1.安装 路由,体验react-router的写法
npm install react-router-dom --save
import React,{Component} from 'react'
import { BrowserRouter,Link,Route,Switch,Redirect} from 'react-router-dom'
function App(){
return (
<div>
{/* 编写路由导航 */}
<ul>
<li> <Link to="/">首页</Link></li>
<li><Link to="/classes">课程</Link></li>
<li><Link to="/mine">我的</Link></li>
<li><Link to="/mineaaaa">没有的组件</Link></li>
</ul>
{/* 路由配置 */}
<Switch>
<Route exact path="/" component={Home}></Route>
<Route path="/classes" component={Classes}></Route>
<Route path="/login" component={Login}></Route>
{/* 嵌套路由别设置确切/精准匹配exact,会造成无法匹配 */}
{/* <Route path="/mine" component={Mine}></Route> */}
<RouteGuard path="/mine" component={Mine}></RouteGuard>
{/* 配置课程详情页,演示路由传参取参 */}
<Route path="/detail/:course" component={Detail}></Route>
{/* 配置404组件 当在地址栏输入没有的地址会跳到404页面 */}
<Route component={Notfound}></Route>
</Switch>
</div>
)}
具体步骤:
- 引入顶层路由组件 BrowserRouter组件 包裹根组件;
- 引入 Link组件编写路由导航:
- 引入route组件编写导航配置
- 编写导航配置对应的 component 组件
2.路由的 传参/取参
1.声明式导航路由配置时配置路由参数
{match, history, location} = this.props // 匹配路由信息对象 本地信息对象 历史信息对象
// 1. 配置
<Route path="/detail/:course" component={Detail}></Route>
// 2. 传递
<Link to="/detail/react">react</Link>
// 3. 取参
{match.params.course} // 解构路由器对象里的match.params获取参数信息
2.编程式导航传递参数与获取
// 解构路由器对象获取到导航对象history(用作命令式导航)
// 通过事件执行,传递的参数装载在state里面
history.push({ pathname: "/", state:{ foo: "bar" } })
//从路由信息解构出location(当前的url信息)对象
// location.state 进行获取
3.嵌套路由和路由重定向
// 1. 一级组件
function App(){
return ( <div>
<ul>
<li><Link to="/mine">我的</Link></li> // 路由导航
</ul>
<Route path="/mine" component={Mine}></Route> // 路由配置
</div> )}
// 2. 二级组件嵌套在一级组件里进行显示
function Mine(){
return ( <div>
<h2>个人中心</h2>
<ul>
<li><Link to="/mine/userinfo">个人信息</Link></li>
<li><Link to="/mine/order">客户订单</Link></li>
</ul>
<Route path="/mine/userinfo" component={()=>(<div>个人信息</div>)}></Route>
<Route path="/mine/order" component={()=>(<div>客户订单</div>)}></Route>
<Redirect to="/mine/userinfo"></Redirect>
</div>
)
// 注意点:嵌套的子路由需要跟随父路由且不设置确切匹配
4.路由守卫
1. 函数型的高阶组件:定义验证功能的路由组件
// 方式一:函数型的高阶组件:定义验证功能的路由组件
function PrivateRoute({component:Comp, ...rest}){ // Comp自己原路由组件 ...rest 自己参数
return (
<Route {...rest}
render={ (props)=>
Auth.isLogin? <Comp {...props}/> : (
<Redirect to={{pathname:'/login',state:{from:props.location}}}
)}
/>
)}
<PrivateRoute path='/about' component={About}></PrivateRoute>
2.redux方式集成用户认证信息
// 让指定的APP.js中<Route path="/about" component={About}> 有验证权限的功能,对Route进行包装
// 路由守卫+redux (实例:将用户共享、登录状态集成到redux中)
// 方式二:redux方式集成用户认证信息
// 1.src目录下新建store目录
// index.js
import{ createStore, applyMiddleware,combineReducers} from "redux";
import logger from "redux-logger";
import thunk from "redux-thunk";
import user from './user.reducer'
const store = createStore(combineReducers({
user }), applyMiddleware(logger, thunk)); // 创建store 有state和reducer的store
export default store;
// user.reducer.js
const initState={
isLogin:false, // 表示用户未登录
userInfo:{}
}
function User(state=initState, action){
switch(action.type){
case 'login':
return { isLogin:true }
default:
return initState;
}
}
export const mapStateToProps = state=>{
return { user:state.user }
}
const login =()=>{
return (dispatch)=>{
setTimeout(()=>{ dispatch({type:'login'}) },1000)
}
}
export const mapDispatchToProps = dispatch =>{
//action 默认接收1个对象,执行下个任务,如果是1个函数,则需要异步处理,react-thunk
return login:()=>dispatch(login())
}
export default User
App.js
import{ connect} from "react-redux";
import{ mapStateToProps} from "./store/user.reducer";
// 高阶组件:定义验证功能的路由组件
@connect(mapStateToProps)
class PrivateRoute extends Component{
render() {
const Comp = this.props.component; // 获取原组件
return(
<Route
{...this.props}
component={
(props) =>
this.props.user.isLogin ?
(<Comp {...props} />) :
(<Redirect to={{ pathname: '/login',state: { from: props.location} }} />)
}>
</Route>
)}}
....
login.js
import React, { Component} from 'react'
import Auth from '../utils/auth';
import{ Button} from "antd";
import{ Redirect} from "react-router-dom";
import{ connect} from "react-redux";
import{ mapStateToProps, mapDispatchToProps} from '../store/user.reducer';
@connect(mapStateToProps,mapDispatchToProps)
class Login extends Component{
handleLogin =() =>{
this.props.login(); // 异步处理
}
render() {
let{ isLogin} = this.props.user;
let path = this.props.location.state.from.pathname
if(isLogin) {
return <Redirect to={path}/>
} else{
return(
<div> <p>请先登录</p>
<Button onClick={this.handleLogin}>登录</Button>
</div>
)}}}
export default Login
pre-note
当有一些页面需要登录之后才有权限去访问时,这时候我们的路由守卫就可以派上用场了,
React里 的路由守卫其实也是一个组件,最后返回的还是Route组件
// 编写路由守卫组件进行权限控制
class RouteGuard extends Component{
render(){
const {component:Component,...otherProps}=this.props;
return (
<Route {...otherProps} render={props=>(
auth.isLogin?<Component {...props}></Component>:
(<Redirect to={
{pathname:'/login',state:{from:props.location.pathname}}
}></Redirect>)
)}></Route>
)}}
//模拟接口
const auth={
isLogin:false,
login(callback){
this.isLogin=true;
setTimeout(callback,1000)
} }
//登录组件
class Login extends Component{
state={ isLogin:false };
login=()=>{
auth.login(()=>{
this.setState({ isLogin:true })
}) }
render(){
//回调地址
const from=(this.props.location.state&&this.props.location.state.from)|| '/'
if(this.state.isLogin){
return <Redirect to={from}></Redirect>
}
return (
<div>
<p>请先登录</p>
<button onClick={this.login}>登录</button>
</div>
) } }