React全家桶学习

一、React简介

1 react特点

React:用于构建用户界面的javascript库。是一个将数据渲染为HTML视图的开源JavaScript库。

react的特点:

1.采用组件化模式、声明式编码,提高开发效率及组件复用率。

2.在React Native中可以使用React语法进行移动端开发。

3.使用虚拟DOM+优秀的Diffing算法,尽量减少与真实DOM的交互。

2 react基本使用

2.1 相关js库

  1. react.js:React核心库。
  2. react-dom.js:提供操作DOM的react扩展库。
  3. babel.min.js:解析JSX语法代码转为JS代码的库。
  4. prop-types.js:用于对组件标签属性进行限制

2.2 创建虚拟DOM的两种方式

  1. 纯JS方式(一般不用)
  2. JSX方式

2.3 虚拟DOM与真实DOM

  1. 本质是Object类型的对象(一般对象)
  2. 虚拟DOM比较“轻”,真实DOM比较“重”,因为虚拟DOM是React内部在使用,无需真实DOM上那么多属性。
  3. 虚拟DOM最终会被React转化成真实DOM,呈现在页面上

2.4 jsx语法规则

1.介绍

        (1)全称:  JavaScript XML

        (2)react定义的一种类似于XML的JS扩展语法: JS + XML本质是                          React.createElement(componentprops, ...children)方法的语法糖

        (3)作用: 用来简化创建虚拟DOM

2.规则

        (1)定义虚拟dom时,不要写引号

        (2)标签中混入JS表达式时要用{}

        (3)样式的类名要用className

        (4)内联样式要用style={{}}的形式去写

        (5)只有一个根标签

        (6)标签必须闭合

        (7)标签首字母

                A.若小写字母开头,则将该标签转为html中同名标签,若html中无该标签对应的

                 同名的元素,则报错。

                B.若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。

const myID = 'aTguigu'
const myData = 'hello,React'
const VDOM = (
    <div>
        <h2 className="title" id={myID.toLowerCase()}>
            <span style={{color:'white',fontSize:'30px'}}>{myData.toUpperCase()}</span>
        </h2>
        <h2 className="title" id={myID.toLowerCase() + '2'}>
            <span style={{color:'white',fontSize:'30px'}}>{myData.toUpperCase()}</span>
        </h2>
    </div>
)
ReactDOM.render(VDOM,document.getElementById('test'))

额外知识:

1. 区分js表达式和js语句

        (1)js表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方

                例如:a 、a+b、demo()、arr.map()、function test()

        (2)js语法(代码):

                例如:if语句、for循环、switch语句

2.react的jsx语法里的{},可以自动遍历数组,不能遍历obj

3.react定义一个数组,如需遍历生成<li></li>,使用data.map()

const data = ['Angular','React','Vue']
const VDOM = (
    <h1>前端js框架列表</h1>
    <ul>
        {
            data.map((item,index) =>{
                return <li key={index}>{item}</li>
            })
        }
    </ul> 
)

2.5 模块与组件、模块化与组件化的理解

1.模块

        (1)理解:向外提供特定功能的js程序,一般就是一个js文件

        (2)为什么要拆成模块:随着业务逻辑的增加,代码越来越多且复杂

        (3)作用:复用js,简化js的编写,提高js的效率

2.组件

        (1)理解:用来实现局部功能效果的代码和资源的集合

        (2)为什么:一个界面的功能更复杂

        (3)作用:复用编码,简化项目编码,提高运行效率

3.模块化

当应用的js都已模块来编写,这个应用就是一个模块化的应用

4.组件化

当应用是以多组件的方式实现,这个应用就是一个组件化的应用

二、react面向组件编程

1  基本理解和使用

(1)使用react开发者工具调试

(2)组件的定义

        A.函数式组件:

                ReactDOM.render(<Demo/>......)之后发生了什么:

                        ① React解析组件标签,找到了Demo组件

                        ② 发现组件是使用函数定义的,调用该函数,将返回的虚拟DOM转为真实DOM

function Demo(){
    console.log(this) //此处的this是underfined,因为babel编译后开启了严格模式
    return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>
}
ReactDOM.render(<Demo/>,document.getElementById('test'))

        B.类式组件

                ReactDOM.render(<Demo/>......)之后发生了什么:

                        ① React解析组件标签,找到了Demo组件

                        ② 发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型                              上的render方法。将render返回的虚拟DOM转为真实DOM,呈现在页面上。

class Demo extends React.Commonent {
    render(){
        // render 是放在哪里的?    -- Demo的原型对象上,供实例使用
        // render中的this是谁?     -- Demo的实例对象 
        return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>
    }
}
ReactDOM.render(<Demo/>,document.getElementById('test'))

2 组件实例的三大核心属性:state

2.1 理解

(1)state是组件对象最重要的属性,值是对象(可以包含多个key-value的组合)

(2)组件被成为“状态机”,通过更改state来更新对应的页面的显示(重新渲染组件)

2.2 注意

(1)组件中的render方法中的this为组件实例对象

(2)组件自定义的方法中this为underfined,如何解决?

        a.强制绑定this,通过函数对象的bind()

        b.箭头函数

(3)状态数据,不能直接修改或更新

class Weather extends React.Component {
    constructor(props){
        super(props)
        // 初始化状态
        this.state = {isHot:true}
        // 解决changeWeather中this指向问题  
        this.changeWeather = this.changeWeather.bind(this)
    }
    render(){
        console.log(this)
        return <h1 onClick={this.changeWeather} className='title'>今天天气很{this.state.isHot ? '炎热' : '寒冷'}</h1>
    }
            
    changeWeather(){
         // changeWeather放在哪里? -- Weather的原型对象上,供实例使用
         // 由于changeWeather是作为onClick的回调,所以changeWeather不是通过实例调用,是直接调用
         // 类中的方法默认开启了局部的严格模式,所以changeWeather中的this为underfined
                
         // 获取原来的isHot值
         const isHot = this.state.isHot
         // 严重注意:状态(state)不可直接更改,要借助一个setState去更改
         // setState更改是合并state的值,不是替换
         this.setState({isHot:!isHot})
     }
}
ReactDOM.render(<Weather/>,document.getElementById('test'))
class Weather extends React.Component {
    state = {isHot:true}
    render(){
        return <h1 onClick={this.changeWeather} className='title'>今天天气很{this.state.isHot ? '炎热' : '寒冷'}</h1>
    }
            
    // 自定义方法 -- 要用赋值语句的形式 + 箭头函数
    changeWeather = ()=>{
        const isHot = this.state.isHot
        this.setState({isHot:!isHot})
    }
}
ReactDOM.render(<Weather/>,document.getElementById('test'))

3 组件实例的三大核心属性:props

3.1 实例中使用props(类式组件)

class Person extends React.Component{
    render(){
        const {name,age,sex} = this.props
        return(
            <ul>
                <li>姓名:{name}</li>
                <li>性别:{sex}</li>
                <li>年龄:{age}</li>
            </ul>
         )
    }
}
//对标签属性类型进行类型、必要性的限制 
Person.propTypes = {
    name:PropTypes.string.isRequired, //限制name属性为字符串,并且必传
    sex:PropTypes.string, //限制name属性为字符串
    age:PropTypes.number, //限制name属性为数值
    speak:PropTypes.func //限制name属性为函数
}
//指定默认的标签属性值
Person.defaultProps = {
    sex:'男',
    age:18
}
ReactDOM.render(<Person name='jerry' speak={speak} />,document.getElementById('test1'))
ReactDOM.render(<Person name='tom' age={18} sex='女' />,document.getElementById('test2'))

const p = {name:'老刘',age:18,sex:'女'}
ReactDOM.render(<Person {...p} />,document.getElementById('test3'))

function speak(){
    console.log('我说话了')
}
class Person extends React.Component{
    static propTypes = {
        name:PropTypes.string.isRequired, 
        sex:PropTypes.string, 
        age:PropTypes.number,
        speak:PropTypes.func
    }
    static defaultProps = {
        sex:'男',
        age:18
    }

    state = {}
    render(){
        const {name,age,sex} = this.props
        return(
            <ul>
                <li>姓名:{name}</li>
                <li>性别:{sex}</li>
                <li>年龄:{age}</li>
            </ul>
        )
    }
}

ReactDOM.render(<Person name='jerry' speak={speak} />,document.getElementById('test1'))
ReactDOM.render(<Person name='tom' age={18} sex='女' />,document.getElementById('test2'))

const p = {name:'老刘',age:18,sex:'女'}
ReactDOM.render(<Person {...p} />,document.getElementById('test3'))

function speak(){
    console.log('我说话了')
}

注意:类中写构造器的情况,只有在构造器需要用到this.props访问props才写。

         类中构造器可以不写的情况下尽量不写。

         构造器是否接收props,是否传递给super,取决于:是佛希望在构造器中通过this访问props

class Person extends React.Component{
    constructor(props){
        super(props)
        console.log('constructor',this.props)
    }
}

3.2 函数式组件中使用props

function Person(props){
const {name,age,sex} = props
    return(
        <ul>
            <li>姓名:{name}</li>
            <li>性别:{sex}</li>
            <li>年龄:{age}</li>
        </ul>
    )
}
Person.propTypes = {
    name:PropTypes.string.isRequired, 
    sex:PropTypes.string, 
    age:PropTypes.number,
    speak:PropTypes.func
}
Person.defaultProps = {
        sex:'男',
        age:18
}
ReactDOM.render(<Person name='jerry' age={19} sex='男' />,document.getElementById('test'))

3.3 理解和作用

(1)每个组件对象都会有props(properties的简写)属性

(2)组件标签的所有属性都保存在props中

(3)通过标签属性从组件外向组件内传递变化的数据

(4)注意:组件内部不要修改props数据

4 组件实例的三大核心属性:refs

4.1 字符串形式的refs(被弃用)

<input ref="input1" type="text" placeholder="点击按钮提示数据" />

4.2 函数回调形式的refs

(1)内联形式的函数回调(大多数情况下使用这种方式)

 <input ref={c => this.input1 = c;console.log('@',c)} type="text" placeholder="点击按钮提示数据" />

        注意:在更新过程中函数会被执行两次,第一次传入参数null,第二次传入DOM元素,即

                   第一次输出@,=null;第二次输出@,DOM元素

        原因:在每一次渲染时会创建一个新的函数实例,所以React清空旧的ref并设置新的。

(2)class的绑定函数(解决更新过程中被执行两次)

<input ref={this.saveRef} type="text" placeholder="点击按钮提示数据" />

saveRef = (c)=>{
    this.input1 = c;
    console.log('@',c)
}

3.3 React.createRef(官网推荐使用)

class Demo extends React.Component{
    myRef = React.createRef()
    myRef2 = React.createRef()
    render(){
        return(
            <div>
                <input ref={this.myRef} type="text" placeholder="点击按钮提示数据" />
                <button onClick={this.showData}>点我提示左侧的数据</button>
                <input onBlur={this.showData2} ref={this.myRef2} type="text" placeholder="失去焦点提示数据" />
            </div>
        )
    }
    showData = ()=>{
        alert(this.myRef.current.value)
    }
    showData2 = ()=>{
        alert(this.myRef2.current.value)
    }  
}
ReactDOM.render(<Demo/>,document.getElementById('test'))

注意:React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,

           该容器是“专人专用”,一个ref标识只能创建有一个容器。

5 事件处理

(1)通过onXxx属性指定事件处理函数(注意大小写)

      1.React使用的是自定义(合成事件),而不是使用的原生DOM事件  -- 为了更好的兼容性

        2.React中的事件是通过事件委托方式处理的(委托给组件最外层的元素) -- 为了高效

(2)通过event.target得到发生事件的Dom元素对象  --  不要过度使用ref

class Demo extends React.Component{
myRef = React.createRef()
render(){
    return(
        <div>
            <input ref={this.myRef} type="text" placeholder="点击按钮提示数据" />
            <button onClick={this.showData}>点我提示左侧的数据</button>
            <input onBlur={this.showData2} type="text" placeholder="失去焦点提示数据" />
        </div>
    )
}
showData = ()=>{
    alert(this.myRef.current.value)
}
showData2 = (event)=>{
    alert(event.target.value)
}  
}
ReactDOM.render(<Demo/>,document.getElementById('test'))

 6 收集表单数据 - 阶函数与函数柯里化

(1)高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数

        1.若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数

        2.若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数

(2)函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数           编码形式

(3)函数柯里化写法:

 class Demo extends React.Component{
    state = {
        username:'',
        password:''
    }
    handleSubmit = (event)=>{
        event.preventDefault()
        alert(`用户名是:${this.state.username},密码是:${this.state.password}`)
    }
    saveFormData = (dataType)=>{
        return (event) => {
            this.setState({[dataType]:event.target.value})
        }
    }
    render(){
        return(
            <form onSubmit={this.handleSubmit}>
                用户名:<input onChange={this.saveFormData('username')} type="text" name="username" />
                密码<input onChange={this.saveFormData('password')} type="password" name="password" />
                <button>提交</button>
            </form>
        )
    }
}
ReactDOM.render(<Demo/>,document.getElementById('test'))

(4)不用函数柯里化写法:

class Demo extends React.Component{
    state = {
        username:'',
        password:''
    }
    handleSubmit = (event)=>{
        event.preventDefault()
        alert(`用户名是:${this.state.username},密码是:${this.state.password}`)
    }
    saveFormData = (dataType,event)=>{
        this.setState({[dataType]:event.target.value})
    }
    render(){
        return(
            <form onSubmit={this.handleSubmit}>
                用户名:<input onChange={ event => this.saveFormData('username',event) } type="text" name="username" />
                密码<input onChange={ event => this.saveFormData('password',event) } type="password" name="password" />
                <button>提交</button>
            </form>
        )
    }
}
ReactDOM.render(<Demo/>,document.getElementById('test'))

7 生命周期

1.组件从创建到死亡会经历一些特定的阶段

2.React组件中包含一系列钩子函数,会在特定的时期调用

3.我们在定义组件时,会在特定的声明周期回调函数中做特定的事情

三、react脚手架

1 初始化脚手架

npm i create-react-app -g
create-react-app 项目名

2 todoList案例

(1)app.js

import React, { Component } from 'react'
import Header from './components/Header'
import List from './components/List'
import Footer from './components/Footer'
import './App.css'

export default class App extends Component {
	//状态在哪里,操作状态的方法就在哪里

	//初始化状态
	state = {todos:[
		{id:'001',name:'吃饭',done:true},
		{id:'002',name:'睡觉',done:true},
		{id:'003',name:'打代码',done:false},
		{id:'004',name:'逛街',done:false}
	]}

	//addTodo用于添加一个todo,接收的参数是todo对象
	addTodo = (todoObj)=>{
		//获取原todos
		const {todos} = this.state
		//追加一个todo
		const newTodos = [todoObj,...todos]
		//更新状态
		this.setState({todos:newTodos})
	}

	//updateTodo用于更新一个todo对象
	updateTodo = (id,done)=>{
		//获取状态中的todos
		const {todos} = this.state
		//匹配处理数据
		const newTodos = todos.map((todoObj)=>{
			if(todoObj.id === id) return {...todoObj,done}
			else return todoObj
		})
		this.setState({todos:newTodos})
	}

	//deleteTodo用于删除一个todo对象
	deleteTodo = (id)=>{
		//获取原来的todos
		const {todos} = this.state
		//删除指定id的todo对象
		const newTodos = todos.filter((todoObj)=>{
			return todoObj.id !== id
		})
		//更新状态
		this.setState({todos:newTodos})
	}

	//checkAllTodo用于全选
	checkAllTodo = (done)=>{
		//获取原来的todos
		const {todos} = this.state
		//加工数据
		const newTodos = todos.map((todoObj)=>{
			return {...todoObj,done}
		})
		//更新状态
		this.setState({todos:newTodos})
	}

	//clearAllDone用于清除所有已完成的
	clearAllDone = ()=>{
		//获取原来的todos
		const {todos} = this.state
		//过滤数据
		const newTodos = todos.filter((todoObj)=>{
			return !todoObj.done
		})
		//更新状态
		this.setState({todos:newTodos})
	}

	render() {
		const {todos} = this.state
		return (
			<div className="todo-container">
				<div className="todo-wrap">
					<Header addTodo={this.addTodo}/>
					<List todos={todos} updateTodo={this.updateTodo} deleteTodo={this.deleteTodo}/>
					<Footer todos={todos} checkAllTodo={this.checkAllTodo} clearAllDone={this.clearAllDone}/>
				</div>
			</div>
		)
	}
}

(2)Header组件

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import {nanoid} from 'nanoid'
import './index.css'

export default class Header extends Component {

	//对接收的props进行:类型、必要性的限制
	static propTypes = {
		addTodo:PropTypes.func.isRequired
	}

	//键盘事件的回调
	handleKeyUp = (event)=>{
		//解构赋值获取keyCode,target
		const {keyCode,target} = event
		//判断是否是回车按键
		if(keyCode !== 13) return
		//添加的todo名字不能为空
		if(target.value.trim() === ''){
			alert('输入不能为空')
			return
		}
		//准备好一个todo对象
		const todoObj = {id:nanoid(),name:target.value,done:false}
		//将todoObj传递给App
		this.props.addTodo(todoObj)
		//清空输入
		target.value = ''
	}

	render() {
		return (
			<div className="todo-header">
				<input onKeyUp={this.handleKeyUp} type="text" placeholder="请输入你的任务名称,按回车键确认"/>
			</div>
		)
	}
}

(3)List组件

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Item from '../Item'
import './index.css'

export default class List extends Component {

	//对接收的props进行:类型、必要性的限制
	static propTypes = {
		todos:PropTypes.array.isRequired,
		updateTodo:PropTypes.func.isRequired,
		deleteTodo:PropTypes.func.isRequired,
	}

	render() {
		const {todos,updateTodo,deleteTodo} = this.props
		return (
			<ul className="todo-main">
				{
					todos.map( todo =>{
						return <Item key={todo.id} {...todo} updateTodo={updateTodo} deleteTodo={deleteTodo}/>
					})
				}
			</ul>
		)
	}
}

(4)Item组件

import React, { Component } from 'react'
import './index.css'

export default class Item extends Component {

	state = {mouse:false} //标识鼠标移入、移出

	//鼠标移入、移出的回调
	handleMouse = (flag)=>{
		return ()=>{
			this.setState({mouse:flag})
		}
	}

	//勾选、取消勾选某一个todo的回调
	handleCheck = (id)=>{
		return (event)=>{
			this.props.updateTodo(id,event.target.checked)
		}
	}

	//删除一个todo的回调
	handleDelete = (id)=>{
		if(window.confirm('确定删除吗?')){
			this.props.deleteTodo(id)
		}
	}


	render() {
		const {id,name,done} = this.props
		const {mouse} = this.state
		return (
			<li style={{backgroundColor:mouse ? '#ddd' : 'white'}} onMouseEnter={this.handleMouse(true)} onMouseLeave={this.handleMouse(false)}>
				<label>
					<input type="checkbox" checked={done} onChange={this.handleCheck(id)}/>
					<span>{name}</span>
				</label>
				<button onClick={()=> this.handleDelete(id) } className="btn btn-danger" style={{display:mouse?'block':'none'}}>删除</button>
			</li>
		)
	}
}

(5)Footer组件

import React, { Component } from 'react'
import './index.css'

export default class Footer extends Component {

	//全选checkbox的回调
	handleCheckAll = (event)=>{
		this.props.checkAllTodo(event.target.checked)
	}

	//清除已完成任务的回调
	handleClearAllDone = ()=>{
		this.props.clearAllDone()
	}

	render() {
		const {todos} = this.props
		//已完成的个数
		const doneCount = todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0),0)
		//总数
		const total = todos.length
		return (
			<div className="todo-footer">
				<label>
					<input type="checkbox" onChange={this.handleCheckAll} checked={doneCount === total && total !== 0 ? true : false}/>
				</label>
				<span>
					<span>已完成{doneCount}</span> / 全部{total}
				</span>
				<button onClick={this.handleClearAllDone} className="btn btn-danger">清除已完成任务</button>
			</div>
		)
	}
}

四、React AJAX

1 理解

(1)前置说明

1. React本身只关注页面,并不包含发送ajax请求的代码

2. 前端应用需要通过ajax请求与后台进行交互

3. react应用中需要集成第三方ajax库(或自己封装)

(2)常用的ajax请求库

1. Jqyery:比较重,如果需要另外引入,不建议使用

2. axios:轻量级,建议使用

        a. 封装XmlHttpRequest对象的ajax

        b.  promise风格

        c. 可以在浏览器端和node服务器端

2 react配置proxy代理

(1)方法一:在package.json中追加配置:

"proxy":"http://localhost:5000"

        说明:

        1. 优点:配置简单,前端请求资源时可以不加任何前缀。

        2. 缺点:不能配置多个代理。

        3. 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000         (优先匹配前端资源)

(2)方法二

        1. 第一步:创建代理配置文件 -- 在src下创建配置文件:src/setupProxy.js

        2. 编写setupProxy.js配置具体代理规则:

const proxy = require('http-proxy-middleware')
   
   module.exports = function(app) {
     app.use(
       proxy('/api1', {  //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)
         target: 'http://localhost:5000', //配置转发目标地址(能返回数据的服务器地址)
         changeOrigin: true, //控制服务器接收到的请求头中host字段的值
         /*
         	changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
         	changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
         	changeOrigin默认值为false,但我们一般将changeOrigin值设为true
         */
         pathRewrite: {'^/api1': ''} //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
       }),
       proxy('/api2', { 
         target: 'http://localhost:5001',
         changeOrigin: true,
         pathRewrite: {'^/api2': ''}
       })
     )
   }

        说明:

        1. 优点:可以配置多个代理,可以灵活的控制请求是否走代理。

        2. 缺点:配置繁琐,前端请求资源时必须加前缀。

3 axios发送请求

	search = ()=>{
		//获取用户的输入(连续解构赋值+重命名)
		const {keyWordElement:{value:keyWord}} = this
		//发送请求前通知App更新状态
		this.props.updateAppState({isFirst:false,isLoading:true})
		//发送网络请求
		axios.get(`/api1/search/users?q=${keyWord}`).then(
			response => {
				//请求成功后通知App更新状态
				this.props.updateAppState({isLoading:false,users:response.data.items})
			},
			error => {
				//请求失败后通知App更新状态
				this.props.updateAppState({isLoading:false,err:error.message})
			}
		)
	}

4 fetch发送请求

	search = async ()=>{
		try {
			const response= await fetch(`/api1/search/users2?q=${keyWord}`)
			const data = await response.json()
			console.log(data);
			PubSub.publish('atguigu',{isLoading:false,users:data.items})
		} catch (error) {
			console.log('请求出错',error);
			PubSub.publish('atguigu',{isLoading:false,err:error.message})
		}
	}

五 react路由

1 路由基本使用

注意:由于react-router-dom在2021年11月升级到6版本,而教程使用的是5版本

npm i react-router-dom@5

1.明确好界面中的导航区、展示区

2.导航区的a标签改为Link标签

<Link to="/xxxxx">Demo</Link>

3.展示区写Route标签进行路径的匹配

<Route path='/xxxx' component={Demo}/>

4.<App>的最外侧包裹了一个<BrowserRouter>或<HashRouter>

ReactDOM.render(
	<BrowserRouter>
		<App/>
	</BrowserRouter>,
	document.getElementById('root')
)
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 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">
								{/* 注册路由 */}
								<Route path="/about" component={About}/>
								<Route path="/home" component={Home}/>
							</div>
						</div>
					</div>
				</div>
			</div>
		)
	}
}

2 路由组件与一般组件

1.写法不同:

        一般组件:<Demo/>

        路由组件:<Route path="/demo" component={Demo}/>

2.存放位置不同:

        一般组件:components

        路由组件:pages

3.接收到的props不同:

        一般组件:写组件标签时传递了什么,就能收到什么

        路由组件:接收到三个固定的属性                 

history:
    go: ƒ go(n)
    goBack: ƒ goBack()
    goForward: ƒ goForward()
    push: ƒ push(path, state)
    replace: ƒ replace(path, state)
location:
    pathname: "/about"
    search: ""
    state: undefined
match:
    params: {}
    path: "/about"
    url: "/about"

3 NavLink与封装NavLink

1. NavLink可以实现路由链接的高亮,通过activeClassName指定样式名  

.atguigu{
    color:red
}

<NavLink activeClassName="atguigu" className="list-group-item" to="/about">About</NavLink>

2. 封装NavLink

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

export default class MyNavLink extends Component {
	render() {
		// console.log(this.props);
		return (
			<NavLink activeClassName="atguigu" className="list-group-item" {...this.props}/>
		)
	}
}
<MyNavLink to="/about">About</MyNavLink>
<MyNavLink to="/home">Home</MyNavLink>

3. 标签体内容是一个特殊的标签属性

4. 通过this.props.children可以获取标签体内容

4 Switch的使用 -- 改用Routers

1.通常情况下,path和component是一一对应的关系。

2.Switch可以提高路由匹配效率(单一匹配)。

3.两个以上的路由就使用Switch包裹路由

<Switch>
    <Route path="/about" component={About}/>
    <Route path="/home" component={Home}/>
    <Route path="/home" component={Test}/>
</Switch>

5 解决多级路径刷新页面样式丢失的问题

<MyNavLink to="/api/about">About</MyNavLink>

1.public/index.html 中 引入样式时不写 ./ 写 / (常用)

2.public/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL% (常用)-- 只适用脚手架

3.使用HashRouter

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

1.默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)

2.开启严格匹配:

<Route exact={true} path="/about" component={About}/>

3.严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由

7 Redirect的使用    

1.一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由

2.具体编码:

<Switch>

    <Route path="/about" component={About}/>

    <Route path="/home" component={Home}/>

    <Redirect to="/about"/>

</Switch>

8 嵌套路由

1.注册子路由时要写上父路由的path值

2.路由的匹配是按照注册路由的顺序进行的

export default class Home extends Component {
	render() {
		return (
				<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>
			)
	}
}

 9 向路由组件传递参数

(1)params参数

a. 路由链接(携带参数):

<Link to='/demo/test/tom/18'}>详情</Link>

b. 注册路由(声明接收):

<Route path="/demo/test/:name/:age" component={Test}/>

c. 接收参数:

this.props.match.params

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}>
									{/* 向路由组件传递params参数 */}
									<Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>
								</li>
							)
						})
					}
				</ul>
				<hr/>
				{/* 声明接收params参数 */}
				<Route path="/home/message/detail/:id/:title" component={Detail}/>
			</div>
		)
	}
}

const DetailData = [
	{id:'01',content:'你好,中国'},
	{id:'02',content:'你好,尚硅谷'},
	{id:'03',content:'你好,未来的自己'}
]
export default class Detail extends Component {
	render() {
		console.log(this.props);
		// 接收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>
		)
	}
}

(2)search参数

路由链接(携带参数):

<Link to='/demo/test?name=tom&age=18'}>详情</Link>

注册路由(无需声明,正常注册即可):

<Route path="/demo/test" component={Test}/>

接收参数:

this.props.location.search

备注:获取到的search是urlencoded编码字符串,需要借助querystring解析

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参数 */}
									<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>
		)
	}
}
import React, { Component } from 'react'
import qs from 'querystring'

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))

		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>
		)
	}
}

(3)state参数

路由链接(携带参数):

<Link to={{pathname:'/demo/test',state:{name:'tom',age:18}}}>详情</Link>

 注册路由(无需声明,正常注册即可):

<Route path="/demo/test" component={Test}/>

接收参数:

this.props.location.state

 备注:刷新也可以保留住参数

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>
		)
	}
}
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>
		)
	}
}

10 编程式路由导航

借助this.props.history对象上的API对操作路由跳转、前进、后退

                            -this.props.history.push()

                            -this.props.history.replace()

                            -this.props.history.goBack()

                            -this.props.history.goForward()

                            -this.props.history.go()

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参数
		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)
	}

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

		// 接收params参数
		// const {id,title} = this.props.match.params 

		// 接收search参数
		// const {search} = this.props.location
		// const {id,title} = qs.parse(search.slice(1))

		// 接收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>
		)
	}
}

11 withRouter的使用

withRouter可以加工一般组件,让一般组件具备路由组件所特有的API

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)

12 BrowserRouter与HashRouter的区别

1.底层原理不一样:

        BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。

         HashRouter使用的是URL的哈希值。

2.path表现形式不一样

        BrowserRouter的路径中没有#,例如:localhost:3000/demo/test

        HashRouter的路径包含#,例如:localhost:3000/#/demo/test

3.刷新后对路由state参数的影响

         (1).BrowserRouter没有任何影响,因为state保存在history对象中。

         (2).HashRouter刷新后会导致路由state参数的丢失!!!

 4.备注:HashRouter可以用于解决一些路径错误相关的问题。

六 redux

1 redux简介

(1)redux是什么

        1. redux是一个专门用做状态管理的js库

        2. 它可以用在react,angular,vue等项目中,但基本和react配合使用

        3. 作用:集中式管理react应用中多个组件共享的状态

(2)什么情况下用redux

        1. 某个状态的状态,需要让其他组件可以随时拿到(共享)

        2. 一个组件需要改变另一个组件的状态(通信)

        3. 总体原则:能不用就不用,如果不用比较吃力才使用

2 redux原理

3 redux的使用

3.1 同步action

1. store.js 

1).引入redux中的createStore函数,创建一个store

2).createStore调用时要传入一个为其服务的reducer

 3).记得暴露store对象

新版本使用这种方式引入store

import { legacy_createStore as createStore} from 'redux'
/* 
	该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/

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

 2.count_reducer.js

1).reducer的本质是一个函数,接收:preState,action,返回加工后的状态

2).reducer有两个作用:初始化状态,加工状态

3).reducer被第一次调用时,是store自动触发的,

        传递的preState是undefined,

         传递的action是:{type:'@@REDUX/INIT_a.2.b.4}

/* 
	1.该文件是用于创建一个为Count组件服务的reducer,reducer的本质就是一个函数
	2.reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action)
*/
import {INCREMENT,DECREMENT} from './constant'

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
	}
}

3.count_action.js

专门用于创建action对象

/* 
	该文件专门为Count组件生成action对象
*/
import {INCREMENT,DECREMENT} from './constant'

export const createIncrementAction = data => ({type:INCREMENT,data})
export const createDecrementAction = data => ({type:DECREMENT,data})

4.constant.js

放置容易写错的type值

/* 
	该模块是用于定义action对象中type类型的常量值,目的只有一个:便于管理的同时防止程序员单词写错
*/
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'

5.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'))

store.subscribe(()=>{
	ReactDOM.render(<App/>,document.getElementById('root'))
})

3.2 异步action

 1. store.js 

/* 
	该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/

//引入createStore,专门用于创建redux中最为核心的store对象
import {createStore,applyMiddleware} from 'redux'
//引入为Count组件服务的reducer
import countReducer from './count_reducer'
//引入redux-thunk,用于支持异步action
import thunk from 'redux-thunk'
//暴露store
export default createStore(countReducer,applyMiddleware(thunk))

 2.count_reducer.js        

/* 
	1.该文件是用于创建一个为Count组件服务的reducer,reducer的本质就是一个函数
	2.reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action)
*/
import {INCREMENT,DECREMENT} from './constant'

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
	}
}

3.count_action.js

/* 
	该文件专门为Count组件生成action对象
*/
import {INCREMENT,DECREMENT} from './constant'

//同步action,就是指action的值为Object类型的一般对象
export const createIncrementAction = data => ({type:INCREMENT,data})
export const createDecrementAction = data => ({type:DECREMENT,data})

//异步action,就是指action的值为函数,异步action中一般都会调用同步action,异步action不是必须要用的。
export const createIncrementAsyncAction = (data,time) => {
	return (dispatch)=>{
		setTimeout(()=>{
			dispatch(createIncrementAction(data))
		},time)
	}
}

4.constant.js

/* 
	该模块是用于定义action对象中type类型的常量值,目的只有一个:便于管理的同时防止程序员单词写错
*/
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'

5.count组件

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

export default class Count extends Component {

	state = {carName:'奔驰c63'}

	/* componentDidMount(){
		//检测redux中状态的变化,只要变化,就调用render
		store.subscribe(()=>{
			this.setState({})
		})
	} */

	//加法
	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(createIncrementAsyncAction(value*1,500))
		// },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>
		)
	

七 react-redux

 

7.1 完整写法

1. 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中状态的改变,如redux的状态发生了改变,那么重新渲染App组件
store.subscribe(()=>{
	ReactDOM.render(<App/>,document.getElementById('root'))
})

 2.app.jsx

import React, { Component } from 'react'
import Count from './containers/Count'
import store from './redux/store'

export default class App extends Component {
	render() {
		return (
			<div>
				{/* 给容器组件传递store */}
				<Count store={store} />
			</div>
		)
	}

3.store.js

/* 
	该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/

//引入createStore,专门用于创建redux中最为核心的store对象
import {createStore,applyMiddleware} from 'redux'
//引入为Count组件服务的reducer
import countReducer from './count_reducer'
//引入redux-thunk,用于支持异步action
import thunk from 'redux-thunk'
//暴露store
export default createStore(countReducer,applyMiddleware(thunk))

4. count_reducer.js

/* 
	1.该文件是用于创建一个为Count组件服务的reducer,reducer的本质就是一个函数
	2.reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action)
*/
import {INCREMENT,DECREMENT} from './constant'

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
	}
}

5. constant.js

/* 
	该模块是用于定义action对象中type类型的常量值,目的只有一个:便于管理的同时防止程序员单词写错
*/
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'

6. count/-action.js

/* 
	该文件专门为Count组件生成action对象
*/
import {INCREMENT,DECREMENT} from './constant'

//同步action,就是指action的值为Object类型的一般对象
export const createIncrementAction = data => ({type:INCREMENT,data})
export const createDecrementAction = data => ({type:DECREMENT,data})

//异步action,就是指action的值为函数,异步action中一般都会调用同步action,异步action不是必须要用的。
export const createIncrementAsyncAction = (data,time) => {
	return (dispatch)=>{
		setTimeout(()=>{
			dispatch(createIncrementAction(data))
		},time)
	}
}

7. containers\Count

//引入Count的UI组件
import CountUI from '../../components/Count'
//引入action
import {
	createIncrementAction,
	createDecrementAction,
	createIncrementAsyncAction
} from '../../redux/count_action'

//引入connect用于连接UI组件与redux
import {connect} from 'react-redux'

/* 
	1.mapStateToProps函数返回的是一个对象;
	2.返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
	3.mapStateToProps用于传递状态
*/
function mapStateToProps(state){
	return {count:state}
}

/* 
	1.mapDispatchToProps函数返回的是一个对象;
	2.返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
	3.mapDispatchToProps用于传递操作状态的方法
*/
function mapDispatchToProps(dispatch){
	return {
		jia:number => dispatch(createIncrementAction(number)),
		jian:number => dispatch(createDecrementAction(number)),
		jiaAsync:(number,time) => dispatch(createIncrementAsyncAction(number,time)),
	}
}

//使用connect()()创建并暴露一个Count的容器组件
export default connect(mapStateToProps,mapDispatchToProps)(CountUI)

8. components\Count

import React, { Component } from 'react'

export default class Count extends Component {

	state = {carName:'奔驰c63'}

	//加法
	increment = ()=>{
		const {value} = this.selectNumber
		this.props.jia(value*1)
	}
	//减法
	decrement = ()=>{
		const {value} = this.selectNumber
		this.props.jian(value*1)
	}
	//奇数再加
	incrementIfOdd = ()=>{
		const {value} = this.selectNumber
		if(this.props.count % 2 !== 0){
			this.props.jia(value*1)
		}
	}
	//异步加
	incrementAsync = ()=>{
		const {value} = this.selectNumber
		this.props.jiaAsync(value*1,500)
	}

	render() {
		//console.log('UI组件接收到的props是',this.props);
		return (
			<div>
				<h1>当前求和为:{this.props.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>
		)
	}
}

7,2 containers\Count的简化

import React, { Component } from 'react'
//引入action
import {
	createIncrementAction,
	createDecrementAction,
	createIncrementAsyncAction
} from '../../redux/count_action'
//引入connect用于连接UI组件与redux
import {connect} from 'react-redux'

//定义UI组件
class Count extends Component {

	state = {carName:'奔驰c63'}

	//加法
	increment = ()=>{
		const {value} = this.selectNumber
		this.props.jia(value*1)
	}
	//减法
	decrement = ()=>{
		const {value} = this.selectNumber
		this.props.jian(value*1)
	}
	//奇数再加
	incrementIfOdd = ()=>{
		const {value} = this.selectNumber
		if(this.props.count % 2 !== 0){
			this.props.jia(value*1)
		}
	}
	//异步加
	incrementAsync = ()=>{
		const {value} = this.selectNumber
		this.props.jiaAsync(value*1,500)
	}

	render() {
		//console.log('UI组件接收到的props是',this.props);
		return (
			<div>
				<h1>当前求和为:{this.props.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>
		)
	}
}

//使用connect()()创建并暴露一个Count的容器组件
export default connect(
	state => ({count:state}),

	//mapDispatchToProps的一般写法
	/* dispatch => ({
		jia:number => dispatch(createIncrementAction(number)),
		jian:number => dispatch(createDecrementAction(number)),
		jiaAsync:(number,time) => dispatch(createIncrementAsyncAction(number,time)),
	}) */

	//mapDispatchToProps的简写
	{
		jia:createIncrementAction,
		jian:createDecrementAction,
		jiaAsync:createIncrementAsyncAction,
	}
)(Count)

 7.3 react-redux优化

(1).容器组件和UI组件整合一个文件

(2).无需自己给容器组件传递store,给<App/>包裹一个<Provider store={store}>即可。

(3).使用了react-redux后也不用再自己检测redux中状态的改变了,容器组件可以自动完成这个工作。

(4).mapDispatchToProps也可以简单的写成一个对象

(5).一个组件要和redux“打交道”要经过哪几步?

         (1).定义好UI组件---不暴露

           (2).引入connect生成一个容器组件,并暴露,写法如下:

                 connect(

                 state => ({key:value}), //映射状态

                {key:xxxxxAction} //映射操作状态的方法

                 )(UI组件)

(4).在UI组件中通过this.props.xxxxxxx读取和操作状态

1. index.js -- 使用Provider传递 store、不用使用store.subscribe

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

ReactDOM.render(
	<Provider store={store}>
		<App/>
	</Provider>,
	document.getElementById('root')
)

2. app.js -- 不用传递store

import React, { Component } from 'react'
import Count from './containers/Count'

export default class App extends Component {
	render() {
		return (
			<div>
				<Count/>
			</div>
		)
	}
}

3. containers\ Conut

import React, { Component } from 'react'
//引入action
import {
	createIncrementAction,
	createDecrementAction,
	createIncrementAsyncAction
} from '../../redux/count_action'
//引入connect用于连接UI组件与redux
import {connect} from 'react-redux'

//定义UI组件
class Count extends Component {

	state = {carName:'奔驰c63'}

	//加法
	increment = ()=>{
		const {value} = this.selectNumber
		this.props.jia(value*1)
	}
	//减法
	decrement = ()=>{
		const {value} = this.selectNumber
		this.props.jian(value*1)
	}
	//奇数再加
	incrementIfOdd = ()=>{
		const {value} = this.selectNumber
		if(this.props.count % 2 !== 0){
			this.props.jia(value*1)
		}
	}
	//异步加
	incrementAsync = ()=>{
		const {value} = this.selectNumber
		this.props.jiaAsync(value*1,500)
	}

	render() {
		//console.log('UI组件接收到的props是',this.props);
		return (
			<div>
				<h1>当前求和为:{this.props.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>
		)
	}
}

//使用connect()()创建并暴露一个Count的容器组件
export default connect(
	state => ({count:state}),

	//mapDispatchToProps的一般写法
	/* dispatch => ({
		jia:number => dispatch(createIncrementAction(number)),
		jian:number => dispatch(createDecrementAction(number)),
		jiaAsync:(number,time) => dispatch(createIncrementAsyncAction(number,time)),
	}) */

	//mapDispatchToProps的简写
	{
		jia:createIncrementAction,
		jian:createDecrementAction,
		jiaAsync:createIncrementAsyncAction,
	}
)(Count)

7.4 数据共享版

1.index.js

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

ReactDOM.render(
	<Provider store={store}>
		<App/>
	</Provider>,
	document.getElementById('root')
)

2. app.js

import React, { Component } from 'react'
import Count from './containers/Count'
import Person from './containers/Person'

export default class App extends Component {
	render() {
		return (
			<div>
				<Count/>
				<hr/>
				<Person/>
			</div>
		)
	}
}

2.redux/actions

        (1)count.js

/* 
	该文件专门为Count组件生成action对象
*/
import {INCREMENT,DECREMENT} from '../constant'

//同步action,就是指action的值为Object类型的一般对象
export const createIncrementAction = data => ({type:INCREMENT,data})
export const createDecrementAction = data => ({type:DECREMENT,data})

//异步action,就是指action的值为函数,异步action中一般都会调用同步action,异步action不是必须要用的。
export const createIncrementAsyncAction = (data,time) => {
	return (dispatch)=>{
		setTimeout(()=>{
			dispatch(createIncrementAction(data))
		},time)
	}
}

        (2)person.js

import {ADD_PERSON} from '../constant'

//创建增加一个人的action动作对象
export const createAddPersonAction = personObj => ({type:ADD_PERSON,data:personObj})

3. redux/reducers

        (1)count.js

/* 
	1.该文件是用于创建一个为Count组件服务的reducer,reducer的本质就是一个函数
	2.reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action)
*/
import {INCREMENT,DECREMENT} from '../constant'

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

        (2)person.js

import {ADD_PERSON} from '../constant'

//初始化人的列表
const initState = [{id:'001',name:'tom',age:18}]

export default function personReducer(preState=initState,action){
	// console.log('personReducer@#@#@#');
	const {type,data} = action
	switch (type) {
		case ADD_PERSON: //若是添加一个人
			return [data,...preState]
		default:
			return preState
	}
}

4. redux/constant.js

/* 
	该模块是用于定义action对象中type类型的常量值,目的只有一个:便于管理的同时防止程序员单词写错
*/
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
export const ADD_PERSON = 'add_person'

5. redux/store.js

/* 
	该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/

//引入createStore,专门用于创建redux中最为核心的store对象
import {createStore,applyMiddleware,combineReducers} from 'redux'
//引入为Count组件服务的reducer
import countReducer from './reducers/count'
//引入为Count组件服务的reducer
import personReducer from './reducers/person'
//引入redux-thunk,用于支持异步action
import thunk from 'redux-thunk'

//汇总所有的reducer变为一个总的reducer
const allReducer = combineReducers({
	he:countReducer,
	rens:personReducer
})

//暴露store
export default createStore(allReducer,applyMiddleware(thunk))

6.containers/Count/index.jsx

import React, { Component } from 'react'
//引入action
import {
	createIncrementAction,
	createDecrementAction,
	createIncrementAsyncAction
} from '../../redux/actions/count'
//引入connect用于连接UI组件与redux
import {connect} from 'react-redux'

//定义UI组件
class Count extends Component {

	state = {carName:'奔驰c63'}

	//加法
	increment = ()=>{
		const {value} = this.selectNumber
		this.props.jia(value*1)
	}
	//减法
	decrement = ()=>{
		const {value} = this.selectNumber
		this.props.jian(value*1)
	}
	//奇数再加
	incrementIfOdd = ()=>{
		const {value} = this.selectNumber
		if(this.props.count % 2 !== 0){
			this.props.jia(value*1)
		}
	}
	//异步加
	incrementAsync = ()=>{
		const {value} = this.selectNumber
		this.props.jiaAsync(value*1,500)
	}

	render() {
		//console.log('UI组件接收到的props是',this.props);
		return (
			<div>
				<h2>我是Count组件,下方组件总人数为:{this.props.renshu}</h2>
				<h4>当前求和为:{this.props.count}</h4>
				<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>
		)
	}
}

//使用connect()()创建并暴露一个Count的容器组件
export default connect(
	state => ({
		count:state.he,
		renshu:state.rens.length
	}),
	{
		jia:createIncrementAction,
		jian:createDecrementAction,
		jiaAsync:createIncrementAsyncAction,
	}
)(Count)

7. containers/Person/index.jsx

import React, { Component } from 'react'
import {nanoid} from 'nanoid'
import {connect} from 'react-redux'
import {createAddPersonAction} from '../../redux/actions/person'

class Person extends Component {

	addPerson = ()=>{
		const name = this.nameNode.value
		const age = this.ageNode.value
		const personObj = {id:nanoid(),name,age}
		this.props.jiaYiRen(personObj)
		this.nameNode.value = ''
		this.ageNode.value = ''
	}

	render() {
		return (
			<div>
				<h2>我是Person组件,上方组件求和为{this.props.he}</h2>
				<input ref={c=>this.nameNode = c} type="text" placeholder="输入名字"/>
				<input ref={c=>this.ageNode = c} type="text" placeholder="输入年龄"/>
				<button onClick={this.addPerson}>添加</button>
				<ul>
					{
						this.props.yiduiren.map((p)=>{
							return <li key={p.id}>{p.name}--{p.age}</li>
						})
					}
				</ul>
			</div>
		)
	}
}

export default connect(
	state => ({yiduiren:state.rens,he:state.he}),//映射状态
	{jiaYiRen:createAddPersonAction}//映射操作状态的方法
)(Person)

7.5 纯函数 -- redux的reducer函数必须是一个纯函数

1.一类特别的函数:只要是同样的输入(实参),必定得到同样的输出(返回)

2. 必须遵守以下一些束缚:

        (1)不得改写参数数据

        (2)不会产生任何副作用,例如网络请求,输入和输出设备

        (3)不能调用Date.now()或者Math.random()等不纯的函数

所以使用return [data,...preState]
而不是perState.unshift(data)等修改原数组的方法
import {ADD_PERSON} from '../constant'

//初始化人的列表
const initState = [{id:'001',name:'tom',age:18}]

export default function personReducer(preState=initState,action){
	// console.log('personReducer@#@#@#');
	const {type,data} = action
	switch (type) {
		case ADD_PERSON: //若是添加一个人
			return [data,...preState]
		default:
			return preState
	}
}

7.6 redux开发者工具

(1).安装插件

yarn add redux-devtools-extension

(2).store中进行配置

import {composeWithDevTools} from 'redux-devtools-extension'

const store = createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)))

(3)谷歌浏览器安装插件 -- Redux DevTools

八 项目打包运行

1. 打包

npm run build

2. 准备服务器并测试运行

npm i serve -g
serve build(文件夹根目录)

九 react扩展

1 setState

    (1). setState(stateChange, [callback])------对象式的setState

            1.stateChange为状态改变对象(该对象可以体现出状态的更改)

            2.callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用 

this.setState({count:count+1},()=>{
    console.log(this.state.count);
})

    (2). setState(updater, [callback])------函数式的setState

            1.updater为返回stateChange对象的函数。

            2.updater可以接收到state和props。

            4.callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。

this.setState((state,props) => {
    return {count:state.count+1}
})

总结:

        1.对象式的setState是函数式的setState的简写方式(语法糖)

        2.使用原则:

                (1).如果新状态不依赖于原状态 ===> 使用对象方式

                (2).如果新状态依赖于原状态 ===> 使用函数方式

                (3).如果需要在setState()执行后获取最新的状态数据,

                    要在第二个callback函数中读取

2 lazyLoad

(1)通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包

 const Login = lazy(()=>import('@/pages/Login'))

   (2)通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading界面   

 <Suspense fallback={<h1>loading.....</h1>}>

        <Switch>

            <Route path="/xxx" component={Xxxx}/>

            <Redirect to="/login"/>

        </Switch>

    </Suspense>

3 Hooks -- 三个常用的Hook

3.1 State Hook

(1). State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作

(2). 语法: const [xxx, setXxx] = React.useState(initValue)  

(3). useState()说明:

        参数: 第一次初始化指定的值在内部作缓存

        返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数

(4). setXxx()2种写法:

        setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值

        setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值

function Demo(){
	const [count,setCount] = React.useState(0)
	const [name,setName] = React.useState('tom')

	function add(){
		// setCount(count+1) 第一种写法
		setCount(count => count+1)
	}
	function changeName(){
		setName('zhangsan')
	}
	
	return(
		<div>
			<h2>当前求和为:{count}</h2>
			<h2>我的名字是:{name}</h2>
			<button onClick={add}>点我+1</button>
			<button onClick={changeName}>点我改名</button>
		</div>
	)
}

3.2 Effect Hook

(1). Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)

(2). React中的副作用操作:

        发ajax请求数据获取

        设置订阅 / 启动定时器

        手动更改真实DOM

(3). 语法和说明:

        useEffect(() => {

          // 在此可以执行任何带副作用操作

          return () => { // 在组件卸载前执行

            // 在此做一些收尾工作, 比如清除定时器/取消订阅等

          }

        }, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行

   

(4). 可以把 useEffect Hook 看做如下三个函数的组合

        componentDidMount()

        componentDidUpdate()

        componentWillUnmount()

function Demo(){
	const [count,setCount] = React.useState(0)

    React.useEffect(()=>{
		let timer = setInterval(()=>{
			setCount(count => count+1 )
		},1000)
		return ()=>{
			clearInterval(timer)
		}
	},[])
	
    function add(){
		setCount(count => count+1)
	}

    //卸载组件的回调
	function unmount(){
		ReactDOM.unmountComponentAtNode(document.getElementById('root'))
	}

	return(
		<div>
			<h2>当前求和为:{count}</h2>
			<button onClick={add}>点我+1</button>
            <button onClick={unmount}>卸载组件</button>
		</div>
	)
}

3.3 Ref Hook

(1). Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据

(2). 语法: const refContainer = useRef()

(3). 作用:保存标签对象,功能与React.createRef()一样

import React from 'react'
import ReactDOM from 'react-dom'

//类式组件
/* class Demo extends React.Component {

	state = {count:0}

	myRef = React.createRef()

	add = ()=>{
		this.setState(state => ({count:state.count+1}))
	}

	unmount = ()=>{
		ReactDOM.unmountComponentAtNode(document.getElementById('root'))
	}

	show = ()=>{
		alert(this.myRef.current.value)
	}

	componentDidMount(){
		this.timer = setInterval(()=>{
			this.setState( state => ({count:state.count+1}))
		},1000)
	}

	componentWillUnmount(){
		clearInterval(this.timer)
	}

	render() {
		return (
			<div>
				<input type="text" ref={this.myRef}/>
				<h2>当前求和为{this.state.count}</h2>
				<button onClick={this.add}>点我+1</button>
				<button onClick={this.unmount}>卸载组件</button>
				<button onClick={this.show}>点击提示数据</button>
			</div>
		)
	}
} */

function Demo(){
	//console.log('Demo');

	const [count,setCount] = React.useState(0)
	const myRef = React.useRef()

	React.useEffect(()=>{
		let timer = setInterval(()=>{
			setCount(count => count+1 )
		},1000)
		return ()=>{
			clearInterval(timer)
		}
	},[])

	//加的回调
	function add(){
		//setCount(count+1) //第一种写法
		setCount(count => count+1 )
	}

	//提示输入的回调
	function show(){
		alert(myRef.current.value)
	}

	//卸载组件的回调
	function unmount(){
		ReactDOM.unmountComponentAtNode(document.getElementById('root'))
	}

	return (
		<div>
			<input type="text" ref={myRef}/>
			<h2>当前求和为:{count}</h2>
			<button onClick={add}>点我+1</button>
			<button onClick={unmount}>卸载组件</button>
			<button onClick={show}>点我提示数据</button>
		</div>
	)
}

export default Demo

4 Fragment

<Fragment><Fragment>

 <></>

import React, { Component,Fragment } from 'react'

export default class Demo extends Component {
	render() {
		return (
			<Fragment key={1}>
				<input type="text"/>
				<input type="text"/>
			</Fragment>
		)
	}
}
import React, { Component,Fragment } from 'react'

export default class Demo extends Component {
	render() {
		return (
			<>
				<input type="text"/>
				<input type="text"/>
			</>
		)
	}
}

5 Context

一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信

使用:

1. 创建Context容器对象:

 const XxxContext = React.createContext()  

2. 渲染子组时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据:   

<xxxContext.Provider value={数据}>

        子组件

</xxxContext.Provider>

3. 后代组件读取数据:

        (1)第一种方式:仅适用于类组件
   

static contextType = xxxContext  // 声明接收context

this.context // 读取context中的value数据

        (2)第二种方式: 函数组件与类组件都可以  -- 官网使用useContext,以官网为主

 <xxxContext.Consumer>
        {

          value => ( // value就是context中的value数据

            要显示的内容

          )

        }
 </xxxContext.Consumer>

注意:

    在应用开发中一般不用context, 一般都它的封装react插件

import React, { Component } from 'react'
import './index.css'

//创建Context对象
const MyContext = React.createContext()
const {Provider,Consumer} = MyContext
export default class A extends Component {

	state = {username:'tom',age:18}

	render() {
		const {username,age} = this.state
		return (
			<div className="parent">
				<h3>我是A组件</h3>
				<h4>我的用户名是:{username}</h4>
				<Provider value={{username,age}}>
					<B/>
				</Provider>
			</div>
		)
	}
}

class B extends Component {
	render() {
		return (
			<div className="child">
				<h3>我是B组件</h3>
				<C/>
			</div>
		)
	}
}

/* class C extends Component {
	//声明接收context
	static contextType = MyContext
	render() {
		const {username,age} = this.context
		return (
			<div className="grand">
				<h3>我是C组件</h3>
				<h4>我从A组件接收到的用户名:{username},年龄是{age}</h4>
			</div>
		)
	}
} */

function C(){
	return (
		<div className="grand">
			<h3>我是C组件</h3>
			<h4>我从A组件接收到的用户名:
			<Consumer>
				{value => `${value.username},年龄是${value.age}`}
			</Consumer>
			</h4>
		</div>
	)
}

6 Component的2个问题

1. 只要执行setState(),即使不改变状态数据, 组件也会重新render() 

2. 只当前组件重新render(), 就会自动重新render子组件 ==> 效率低

 效率高的做法:只有当组件的state或props数据发生改变时才重新render()

原因:Component中的shouldComponentUpdate()总是返回true

 解决:

(1)办法1:

        重写shouldComponentUpdate()方法

        比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false

(2)办法2:  

        使用PureComponent

        PureComponent重写了shouldComponentUpdate(), 只有state或props数据有变化才返回true

 注意:

        A. 只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false  

        B. 不要直接修改state数据, 而是要产生新数据

        C. 项目中一般使用PureComponent来优化

import React, { PureComponent } from 'react'
import './index.css'

export default class Parent extends PureComponent {

	state = {carName:"奔驰c36",stus:['小张','小李','小王']}

	addStu = ()=>{
		/* const {stus} = this.state
		stus.unshift('小刘')
		this.setState({stus}) */

		const {stus} = this.state
		this.setState({stus:['小刘',...stus]})
	}

	changeCar = ()=>{
		//this.setState({carName:'迈巴赫'})

		const obj = this.state
		obj.carName = '迈巴赫'
		console.log(obj === this.state);
		this.setState(obj)
	}

	/* shouldComponentUpdate(nextProps,nextState){
		// console.log(this.props,this.state); //目前的props和state
		// console.log(nextProps,nextState); //接下要变化的目标props,目标state
		return !this.state.carName === nextState.carName
	} */

	render() {
		console.log('Parent---render');
		const {carName} = this.state
		return (
			<div className="parent">
				<h3>我是Parent组件</h3>
				{this.state.stus}&nbsp;
				<span>我的车名字是:{carName}</span><br/>
				<button onClick={this.changeCar}>点我换车</button>
				<button onClick={this.addStu}>添加一个小刘</button>
				<Child carName="奥拓"/>
			</div>
		)
	}
}

class Child extends PureComponent {

	/* shouldComponentUpdate(nextProps,nextState){
		console.log(this.props,this.state); //目前的props和state
		console.log(nextProps,nextState); //接下要变化的目标props,目标state
		return !this.props.carName === nextProps.carName
	} */

	render() {
		console.log('Child---render');
		return (
			<div className="child">
				<h3>我是Child组件</h3>
				<span>我接到的车是:{this.props.carName}</span>
			</div>
		)
	}
}

7 render props

1. 如何向组件内部动态传入带内容的结构(标签)?

        Vue中:

                使用slot技术, 也就是通过组件标签体传入结构  <AA><BB/></AA>

        React中:

                使用children props: 通过组件标签体传入结构

                使用render props: 通过组件标签属性传入结构, 一般用render函数属性

2. children props

<A>
    <B>xxxx</B>
</A>


{this.props.children}

问题: 如果B组件需要A组件内的数据, ==> 做不到

3. render props

<A render={(data) => <C data={data}></C>}></A>
A组件: {this.props.render(内部state数据)}
C组件: 读取A组件传入的数据显示 {this.props.data} 
import React, { Component } from 'react'
import './index.css'
import C from '../1_setState'

export default class Parent extends Component {
	render() {
		return (
			<div className="parent">
				<h3>我是Parent组件</h3>
				<A render={(name)=><C name={name}/>}/>
			</div>
		)
	}
}

class A extends Component {
	state = {name:'tom'}
	render() {
		console.log(this.props);
		const {name} = this.state
		return (
			<div className="a">
				<h3>我是A组件</h3>
				{this.props.render(name)}
			</div>
		)
	}
}

class B extends Component {
	render() {
		console.log('B--render');
		return (
			<div className="b">
				<h3>我是B组件,{this.props.name}</h3>
			</div>
		)
	}
}

8 错误边界

错误边界:用来捕获后代组件错误,渲染出备用页面

特点:只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件

           在合成事件、定时器中产生的错误。

使用方式:getDerivedStateFromError配合componentDidCatch

// 生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {
    console.log(error);
    // 在render之前触发
    // 返回新的state
    return {
        hasError: true,
    };
}

componentDidCatch(error, info) {
    // 统计页面的错误。发送请求发送到后台去
    console.log(error, info);
}

9 组件通信方式总结

9.1 方式

props:

        (1).children props

        (2).render props

消息订阅-发布:pubs-sub、event等等

集中式管理:redux、dva等等

conText:生产者-消费者模式

9.2 组件间的关系

父子组件:props

兄弟组件(非嵌套组件):消息订阅-发布、集中式管理

祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(用的少)

十 React Router6

1 概述

1. React Router以三个不同的包发布到npm上,它们分别是:

        1. react-router:路由的核心库,提供了很多的:组件、钩子。

        2. react-router-dom:包含react-router所有内容,并添加一些专门用于DOM的组件,例如:

                <BrowserRouter>等

        3. react-router-native:包括react-router所有内容,并添加一些专门用于ReactNative的Api,              例如:<NativeRouter>等

2. 与React Router5版本相比,改变了什么?

        1. 内置组件的变化:移除<Switch>,新增<Routers>等

        2. 语法的变化:component={About /}变为element={<About/>}等

        3. 新增多个hook:useParams、useNavigate、useMatch等

        4. 官方明确推荐函数式组件

2 Component

1. <BrowerRouter>

2. <HashRouter>

3. <Routes /> 与<Route/>

        1. v6版本中移除了先前的<Switch>,引入了新的替代者<Routes>

        2. <Routes>和<Route>要配合使用,且必须要用<Routes>包裹<Route>

        3. <Route>相当于一个if语句,如果其路径与当前URL匹配,则呈现其对应的组件

        4. <Routes caseSensitive>属性用于指定:匹配时是否区分大小写(默认为false)

        5. 当URL发生变化时,<Routes>都会查看其所有子<Routes>元素

            以找到最佳匹配并呈现组件

        6. <Route>也可以嵌套使用,且可配合useRoutes()配置路由表,

            但需要通过<Outlet>组件来渲染子路由

import React from 'react'
import { NavLink,Routes,Route,Navigate } from 'react-router-dom'
import About from './pages/About'
import Home from './pages/Home'

export default function App() {
    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">
                        {/* 路由导航 */}
                        <NavLink className="list-group-item" to="/about">About</NavLink>
                        <NavLink className="list-group-item" to="/home">Home</NavLink>
                    </div>
                </div>
                <div className="col-xs-6">
                    <div className="panel">
                        <div className="panel-body">
                            {/* 注册路由 */}
                            <Routes>
                                <Route path="/about" element={<About/>} />
                                <Route path="/home" element={<Home/>} />
                                <Route path="/" element={<Navigate to="/about" />} />
                            </Routes>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    )
}

4. NavLink高亮效果

1. App.css

.activeClass{
    background: orange;
    color: aliceblue;
}

2. app.jsx

function computedClassName({isActive}){
    return isActive ? 'list-group-item activeClass' : 'list-group-item'
}

<NavLink className={computedClassName} to="/about">About</NavLink>
<NavLink className={computedClassName} to="/home">Home</NavLink>

5 useRoutes路由表使用

1. 新建routes文件夹,新建index.js

import About from '../pages/About'
import Home from '../pages/Home'
import {Navigate} from 'react-router-dom'

const routes = [
    {
        path:'/about',
        element:<About/>
    },
    {
        path:'/home',
        element:<Home/>
    },
    {
        path:'/',
        element:<Navigate to="/about"/>
    }
]

export default routes

2. app.jsx使用

import React from 'react'
import { NavLink,useRoutes } from 'react-router-dom'
import routes from './routes'

export default function App() {
    // 根据路由表生成对应的路由规则
    const element = useRoutes(routes)
    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">
                        {/* 路由导航 */}
                        <NavLink className="list-group-item" to="/about">About</NavLink>
                        <NavLink className="list-group-item" to="/home">Home</NavLink>
                    </div>
                </div>
                <div className="col-xs-6">
                    <div className="panel">
                        <div className="panel-body">
                            {element}
                        </div>
                    </div>
                </div>
            </div>
        </div>
    )
}

6 嵌套路由

1. routes/index.js

import About from '../pages/About'
import Home from '../pages/Home'
import Message from '../pages/Message'
import News from '../pages/News'
import { Navigate } from 'react-router-dom'

const routes = [
    {
        path: '/about',
        element: <About />
    },
    {
        path: '/home',
        element: <Home />,
        children: [{
            path: 'news',
            element: <News />,
        },{
            path: 'message',
            element: <Message />,
        }]
    },
    {
        path: '/',
        element: <Navigate to="/about" />
    }
]

export default routes

2.Home.jsx

import React from 'react'
import { NavLink,Outlet } from 'react-router-dom'
export default function Home() {
  return (
    <div>
      <h2>Home组件内容</h2>
      <div>
        <ul className="nav nav-tabs">
          <li>
            <NavLink className="list-group-item" to="/home/news">News</NavLink>
          </li>
          <li>
            <NavLink className="list-group-item" to="/home/message">Message</NavLink>
          </li>
        </ul>
        {/* 指定路由组件呈现的位置 */}
        <Outlet/>
      </div>
    </div>
  )
}

7 路由Params传参

1. routes/index.js占位

{
            path: 'message',
            element: <Message />,
            children: [
                {
                    path: 'detail/:id/:title/:content',
                    element: <Detail />,
                }
            ]
        }

2. Message.jsx传值

<Link to={`detail/${m.id}/${m.title}/${m.content}`}>{m.title}</Link>

3. Detail.jsx接收参数 -- useParams

import React from 'react'
import { useParams, useMatch } from 'react-router-dom'

export default function Detail() {
    const { id, title, content } = useParams()

    // const x = useMatch('/home/message/detail/:id/:title/:content')
    return (
        <ul>
            <li>消息编号:{id}</li>
            <li>消息标题:{title}</li>
            <li>消息内容:{content}</li>
        </ul>
    )
}

8 路由search传参

1. message.jsx传递参数

<Link to={`detail?id=${m.id}&title=${m.title}&content=${m.content}`}>{m.title}</Link>

2. Detail.jsx接收参数

import React from 'react'
import { useSearchParams,useLocation } from 'react-router-dom'

export default function Detail() {
    const [search,setSearch] = useSearchParams()
    const id = search.get('id')
    const title = search.get('title')
    const content = search.get('content')

    const x = useLocation ()
    console.log(x)
    return (
        <ul>
            <li>
                <button onClick={()=>{setSearch('id=005&title=哈哈&content=嘻嘻')}}>点我更新收到的search参数</button>
            </li>
            <li>消息编号:{id}</li>
            <li>消息标题:{title}</li>
            <li>消息内容:{content}</li>
        </ul>
    )
}

9 路由state传参

1. Message.jsx传参

<Link 
    to="detail"
    state={{
        id:m.id,
        title:m.title,
        content:m.content
    }}
>{m.title}</Link>

2.Detail.jsx接收参数

import React from 'react'
import { useLocation } from 'react-router-dom'
export default function Detail() {
    const {state} = useLocation()
    return (
        <ul>
            <li>消息编号:{state.id}</li>
            <li>消息标题:{state.title}</li>
            <li>消息内容:{state.content}</li>
        </ul>
    )
}

10 编程式路由导航 -- useNavigate

1.使用

import React, { useState } from 'react'
import{Link,Outlet} from 'react-router-dom'
import { useNavigate } from 'react-router-dom'

export default function Message() {
    const [messages] = useState([
        { id: '001', title: '消息1', content: '消息1的内容' },
        { id: '002', title: '消息2', content: '消息2的内容' },
        { id: '003', title: '消息3', content: '消息3的内容' },
        { id: '004', title: '消息4', content: '消息4的内容' }
    ])

    const navigate = useNavigate()

    function showDetail(m){
        navigate('detail',{
            replace:false,
            state:{
                id:m.id,
                title:m.title,
                content:m.content
            }
        })
    }
    return (
        <div>
            <ul>
                {
                    messages.map((m) => {
                        return (
                            <li key={m.id}>
                                <Link 
                                    to="detail"
                                    state={{
                                        id:m.id,
                                        title:m.title,
                                        content:m.content
                                    }}
                                >{m.title}</Link>
                                <button onClick={()=>{showDetail(m)}}>查看详情</button>
                            </li>
                        )
                    })
                }
            </ul>
            <hr/>
            <Outlet />
        </div>
    )
}

2. 前进后退

import React from 'react'
import { useNavigate } from 'react-router-dom'

export default function Header() {
    const navigate = useNavigate()
    function back(){
        navigate(-1)
    }
    function forward(){
        navigate(1)
    }
    return (
        <div className="col-xs-offset-2 col-xs-8">
            <div className="page-header"><h2>React Router Demo</h2></div>
            <button onClick={back}>后退</button>
            <button onClick={forward}>前进</button>
        </div>
    )
}

11 useInRouterContext()

作用:如果组件在<Router>的上下文中呈现,则useInRouterContext钩子返回true,否则返回false

12 useNavigationType()

1. 作用:返回当前的导航类型(用户是如何来到当前页面的)

2. 返回值:POP、PUSH、REPLACE

3. 备注:POP是指在浏览器中直接打开了这个路由的文件(刷新页面) 

13 useOutlet()

1. 作用:用来呈现当前组件中渲染的嵌套路由

const result = useOutlet()
console.log(result)

//如果嵌套的路由没有挂载,则result为null
//如果嵌套的路由已经挂载,则展示嵌套的路由对象

14 useResolvePath()

作用:给定一个URL值,解析其中的:path、search、hash值

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值