当前位置:   article > 正文

React 脚手架_react public

react public

React 脚手架

/css 就相当于 public 中的css 文件夹。/ 就是 public

创建项目并启动

1、全局安装 react

npm i -g create-react-app
  • 1

2、创建项目

切换到想要创建项目的目录

create-react-app 项目名
eg: create-react-app hello-react
  • 1
  • 2

在这里插入图片描述

在这里插入图片描述

3、启动测试

cd hello-react
npm start
  • 1
  • 2

在这里插入图片描述

在这里插入图片描述

文件说明

public ---- 静态资源文件夹
		favicon.icon ------ 网站页签图标
		index.html -------- 主页面(重要)
		logo192.png ------- logo图
		logo512.png ------- logo图
		manifest.json ----- 应用加壳的配置文件
		robots.txt -------- 爬虫协议文件
src ---- 源码文件夹
		App.css -------- App组件的样式
		App.js --------- App组件(重要)
		App.test.js ---- 用于给App做测试
		index.css ------ 样式
		index.js ------- 入口文件(重要)
		logo.svg ------- logo图
		reportWebVitals.js
			--- 页面性能分析文件(需要web-vitals库的支持)
		setupTests.js
			---- 组件单元测试的文件(需要jest-dom库的支持)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

模拟脚手架文件,写出新页面

将脚手架自动生成的 public 、src 文件删除(移动到其他地方)

创建 public、src 文件夹

在 public 文件夹中创建 index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="root"></div>
</body>
</html>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

在 src 文件夹中创建 index.js 程序入口

// 引入核心库 
import React from 'react'
// 引入 ReactDOM
import ReactDOM from 'react-dom'
// 引入 app 组件(.js/.jsx 后缀可以省略)
import App from './App'

// 渲染页面
ReactDOM.render(<App/>,document.getElementById("root"))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

在 src 文件夹中创建 App.js

// 创建 组件 App
import {React,Component} from 'react'
import Hello from './components/Hello/Hello'

// 创建并暴露组件
export default class App extends Component{
    render(){
        return (
            <div>
                <Hello/>
            </div>
        )
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

在 src 文件夹下创建 components 文件夹,以后组件都存放在这里面了。一个组件一个文件夹。

在 src/components 文件夹中创建 Hello 文件夹

在 src/components/Hello 文件夹中创建 Hello.css
.hello{
    font-size: 20px;
    color: red;
}
  • 1
  • 2
  • 3
  • 4
在 src/components/Hello 文件夹中创建 Hello.jsx

为了 和 原生的 js 文件区分,以后组件都使用 jsx

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


export default class Hello extends Component {
    render(){
        return(
            <div>
                <span className='hello'>Hello React !!!</span>
            </div>
        )
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

样式模块化

不是必要的

以后开发过程中会有很多个组件,很多个组件样式。但是他们最终都会被聚集到 App.js 组件中。所以如果多个组件中样式名称一致的话,就会产生冲突。

如果能保证所有组件中的样式名称不冲突,可以不做样式模块化

1、将 Hello.css 名称修改为 Hello.module.css

2、修改 jsx 中的代码

import {React,Component} from 'react'
import hello from './Hello.module.css'


export default class Hello extends Component {
    render(){
        return(
            <div>
                <span className={hello.hello}>Hello React !!!</span>
            </div>
        )
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

安装 react 插件,配置代码模板

写代码会有提示,以及一些代码片段

在这里插入图片描述

案例 TODOList

  • 显示所有 TODO 列表
  • 输入文本,点击按钮显示到列表的首位,并清除输入的文本

该案例,需要将组件分为四个 :Header、List、Item、Footer

入口文件 index.js

import React,{Component} from 'react'
import ReactDOM from 'react-dom'
import App from './App'

ReactDOM.render(<App/>,document.getElementById("root"))
  • 1
  • 2
  • 3
  • 4
  • 5

组件外壳 App.js

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

export default class App extends Component {
    state = {todos:[
        {id:'01',name:'java',done:true},
        {id:'02',name:'react',done:false},
        {id:'03',name:'vue',done:true}
    ]}
    // 用于添加 todo ,接收的参数是 todo 对象
    addToDo = (todo)=>{
        // 获取原 todos
        const {todos} = this.state
        // 追加一个todo
        const newTodos = [todo,...todos]
        this.setState({todos:newTodos})
    }

    // 修改 todo 是否选中
    updateTodo = (id,done)=> {
        // 获取状态中的 todos
        const {todos} = this.state
        // 遍历 todos 处理数据
        const newtodo = todos.map((todo)=>{
            if(todo.id === id) return {...todo,done}
            else return todo
        })
        // 更新数据
        this.setState({todos:newtodo})
    }

    // 删除 todo 对象
    deleteTodo = (id) => {
        // 获取原来的 todos
        const {todos} = this.state
        // 删除指定 id 的 todo 对象
        const newTodo = todos.filter((todo)=>{
            return todo.id !== id
        })
        this.setState({todos:newTodo})
    }

    // 全选功能
    checkAll = (done) => {
        // 获取原来的 todos
        const {todos} = this.state
        // 处理数据
        const newTodo = todos.map((todo)=>{
            return {...todo,done}
        })
        // 更新状态中的值
        this.setState({todos:newTodo})
    }

    // 清除所有已完成的 todo
    deleteAllTodo = ()=>{
        // 获取原来的数据
        const {todos} = this.state
        // 加工数据
        const newTodo = todos.filter((todo)=>{
            return todo.done === false
        })
        // 更新状态数据
        this.setState({todos:newTodo})
    }

    render() {
        return (
            <div className="todo-container">
                <div className="todo-wrap">
                    <Header addToDo={this.addToDo}/>
                    <List handleCheck={this.updateTodo} deleteTodo={this.deleteTodo} todos={this.state.todos}/>
                    <Footer deleteAllTodo={this.deleteAllTodo} checkAll={this.checkAll} todos={this.state.todos}/>
                </div>
            </div>
        )
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81

App.css

/*base*/
body {
    background: #fff;
}

.btn {
    display: inline-block;
    padding: 4px 12px;
    margin-bottom: 0;
    font-size: 14px;
    line-height: 20px;
    text-align: center;
    vertical-align: middle;
    cursor: pointer;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
    border-radius: 4px;
}

.btn-danger {
    color: #fff;
    background-color: #da4f49;
    border: 1px solid #bd362f;
}

.btn-danger:hover {
    color: #fff;
    background-color: #bd362f;
}

.btn:focus {
    outline: none;
}

.todo-container {
    width: 600px;
    margin: 0 auto;
}
.todo-container .todo-wrap {
    padding: 10px;
    border: 1px solid #ddd;
    border-radius: 5px;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

Header 组件

Header.jsx

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

export default class Header extends Component {

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

    handleKey = (event) => {
        const { target, keyCode } = event
        // 回车键的 code 值为13,如果不是按回车则不进行操作
        if (keyCode !== 13) return 
        // 添加的 todo 名字不能为空
        if(target.value.trim() === ''){
            alert("输入不能为空")
            return
        }
        // 准备一个todo对象
        const todo = {id:nanoid(),name:target.value,done:false}
        // 清空文本框
        target.value = ''
        // 将 todo 对象传给父组件(App)
        this.props.addToDo(todo) 
    }
    render() {
        return (
            <div className="todo-header">
                    <input type="text" onKeyUp={this.handleKey} placeholder="请输入你的任务名称,按回车键确认" />
            </div>
            )
        }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

Header.css

/*header*/
.todo-header input {
    width: 560px;
    height: 28px;
    font-size: 14px;
    border: 1px solid #ccc;
    border-radius: 4px;
    padding: 4px 7px;
}

.todo-header input:focus {
    outline: none;
    border-color: rgba(82, 168, 236, 0.8);
    box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

List 组件

List.jsx

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

export default class List extends Component {

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

    render() {
        const {todos,handleCheck,deleteTodo} = this.props
        return (
            <ul className="todo-main">
                {
                    todos.map(todo=>{
                        return <Item handleCheck={handleCheck} deleteTodo={deleteTodo} {...todo} key={todo.id}/>
                    })
                }
            </ul>
        )
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

List.css

/*main*/
.todo-main {
    margin-left: 0px;
    border: 1px solid #ddd;
    border-radius: 2px;
    padding: 0px;
}

.todo-empty {
    height: 40px;
    line-height: 40px;
    border: 1px solid #ddd;
    border-radius: 2px;
    padding-left: 5px;
    margin-top: 10px;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

Item 组件

Item.jsx

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

export default class Item extends Component {
    state = {mouse:false}
    // 鼠标移入、移除的回调
    handleMouse = (flag)=>{
        return () => {
            this.setState({mouse:flag})
        }
    }

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

    // 删除一个 todo
    handleDelete = (id) => {
        return ()=>{
            // window.confirm("确认删除吗?") 点击确认返回true,点击取消返回 false
            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" onChange={this.handleCheck(id)} checked={done}/>
                    <span>{name}</span>
                </label>
                <button onClick={this.handleDelete(id)} className="btn btn-danger" style={{ display: mouse?'block':'none' }}>删除</button>
            </li>
        )
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

Item.css

/*item*/
li {
    list-style: none;
    height: 36px;
    line-height: 36px;
    padding: 0 5px;
    border-bottom: 1px solid #ddd;
}

li label {
    float: left;
    cursor: pointer;
}

li label li input {
    vertical-align: middle;
    margin-right: 6px;
    position: relative;
    top: -1px;
}

li button {
    float: right;
    display: none;
    margin-top: 3px;
}

li:before {
    content: initial;
}

li:last-child {
    border-bottom: none;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

Footer 组件

Footer.jsx

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

export default class Footer extends Component {
    // 全选功能
    handleCheckAll = (event) => {
        this.props.checkAll(event.target.checked)
    }

    // 清除所有已完成的 todo
    deleteAllDone = ()=>{
        // window.confirm("确认删除吗?") 点击确认返回true,点击取消返回 false
        if(window.confirm("确认删除吗?")){
            this.props.deleteAllTodo()
        }
    }

    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.deleteAllDone} className="btn btn-danger">清除已完成任务</button>
            </div>
        )
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

Footer.css

/*footer*/
.todo-footer {
    height: 40px;
    line-height: 40px;
    padding-left: 6px;
    margin-top: 5px;
}

.todo-footer label {
    display: inline-block;
    margin-right: 20px;
    cursor: pointer;
}

.todo-footer label input {
    position: relative;
    top: -1px;
    vertical-align: middle;
    margin-right: 5px;
}

.todo-footer button {
    float: right;
    margin-top: 5px;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

总结

1、拆分组件、实现静态组件。注意 :className、style 的写法

2、动态初始化列表,如何确定将数据放在那个组件的 state 中

  • 某个组件使用 :放在其自身的 state 中
  • 某些组件使用 :放在他们共同的父组件 state 中(官方称此操作为 :状态提升)

3、关于父子之间通信 :

  • 【父组件】 给 【子组件】 传递数据 :通过 props 传递
  • 【子组件】 给 【父组件】 传递数据 :通过 props 传递,要求父提前给子传递一个函数

4、注意 defaultChecked 和 checked 区别。类似的还有 defaultValue 和 value

  • defaultChecked 只有在第一次指定的时候才起作用

5、状态在哪里,操作状态的方法就在那里(this.setState())

React 中 ajax (axios)

常用的 Ajax 库 :

  • jQuery :比较重,不推荐
  • axios :轻量级,建议使用
    • 封装 XmlHttpRequest 对象的 ajax
    • promise 风格

给项目添加库,axios

npm add axios
  • 1

案例 :获取学生列表

1、打开服务器(资料其他文件夹中提供)

node server1.js
  • 1

2、在 App.js 文件中

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

export default class App extends Component {
  getStudent = () => {
    axios.get('http://localhost:5000/students').then(
      (response) => {console.log(response)}
    );
  }
  render() {
    return (
      <div>
        <button onClick={this.getStudent}>点击获取学生数据</button>
      </div>
    )
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

这样会发生跨域

方法一 :只能配置一个

在 package.json 文件中添加配置

"proxy":"http://localhost:5000"
  • 1

在这里插入图片描述

修改 App.js 文件中代码

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

export default class App extends Component {
  getStudent = () => {
    // 服务器上的端口是 5000,因为我们配置了代理,所以我们直接访问 3000,去访问代理
    // 3000 为脚手架的端口
    // 发送请求的时候会先在本地找资源,找不到在去服务器找
    axios.get('http://localhost:3000/students').then(
      (response) => {console.log(response)}
    );
  }
  render() {
    return (
      <div>
        <button onClick={this.getStudent}>点击获取学生数据</button>
      </div>
    )
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
方法二 :可以配置多个

在 src 目录下创建 setupProxy.js 文件,该文件不能使用 ES6 语法

固定写法 :

const {createProxyMiddleware} = require('http-proxy-middleware')

module.exports = function(app) {
  app.use(
    createProxyMiddleware('/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': ''} //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
    }),
    createProxyMiddleware('/api2', { 
      target: 'http://localhost:5001',
      changeOrigin: true,
      pathRewrite: {'^/api2': ''}
    })
  )
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

修改 App.js 文件中代码

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

export default class App extends Component {
  getStudent = () => {
    axios.get('http://localhost:3000/api1/students').then(
      (response) => {console.log(response)}
    );
  }
  getCar = () => {
    axios.get('http://localhost:3000/api2/cars').then(
      (response) => {console.log(response)}
    );
  }
  render() {
    return (
      <div>
        <button onClick={this.getStudent}>点击获取学生数据</button>
        <button onClick={this.getCar}>点击获取汽车数据</button>
      </div>
    )
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

axios 综合案例

实现一个搜索框

该案例,需要将组件分为四个 :Header、List、Item、Footer

组件文件夹都放在 src/components 文件夹中

1、引入 bootstrap.css 样式

在 public 文件夹下创建一个 css 文件夹,将 bootstrap.css 文件引入。在 public/index.html 文件中引入样式文件

<link rel="stylesheet" href="./css/bootstrap.css">
  • 1

2、入口程序 index.js

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

ReactDOM.render(<App/>,document.getElementById("root"))
  • 1
  • 2
  • 3
  • 4
  • 5

3、组件外壳 App.js

App.js
import React, { Component } from 'react'
import List from './components/List/List'
import Search from './components/Search/Search'

export default class App extends Component {

    state = {
        users:[],// users 初始状态
        isFirst:true,// 是否为第一次打开页面
        isLoding:false,// 表示是否处于加载中
        err:''// 存储请求错误信息
    }

    // 改变 State 
    updateAppState = (stateObj) => {
        this.setState(stateObj)
    }

    render() {
        return (
            <div className="container">
                <Search updateAppState={this.updateAppState}/>
                <List {...this.state}/>
            </div>
        )
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

4、创建组件 List

List.jsx
import React, { Component } from 'react'
import './List.css'
export default class List extends Component {
    render() {
        const {users,isFirst,isLoding,err} = this.props
        return (
            <div className="row">
                {
                    isFirst ? <h2>欢迎访问!!!</h2> : 
                    isLoding ? <h2>Loding ...... </h2> : 
                    err ? <h2>{err}</h2> : 
                    users.length===0 ? <h2>为搜索到相关用户</h2> : 
                    users.map((user) => {
                        return (
                            <div key={user.id} className="card">
                                <a rel="noreferrer" href={user.html_url} target="_blank">
                                    <img alt='head_photo' src={user.avatar_url} style={{ width: '100px' }} />
                                </a>
                                <p className="card-text">{user.login}</p>
                            </div>
                        )
                    })
                }
            </div>
        )
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
List.css
.album {
    min-height: 50rem; /* Can be removed; just added for demo purposes */
    padding-top: 3rem;
    padding-bottom: 3rem;
    background-color: #f7f7f7;
}

.card {
    float: left;
    width: 33.333%;
    padding: .75rem;
    margin-bottom: 2rem;
    border: 1px solid #efefef;
    text-align: center;
}

.card > img {
    margin-bottom: .75rem;
    border-radius: 100px;
}

.card-text {
    font-size: 85%;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

5、创建组件 Search.jsx

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

export default class Search extends Component {
    // 处理键盘输入的值
    search = () => {
        // 获取用户的输入
        const { value } = this.inputElement
        // 发送请求前通知 app 改变状态
        this.props.updateAppState({isFirst:false,isLoding:true})
        // 发起网络请求
        axios.get(`http://localhost:3000/api1/search/users?q=${value}`).then(
            (response) => {
                // 发送请求前通知 app 改变状态
                this.props.updateAppState({users:response.data.items,isLoding:false})
            },
            (error) => {
                // 发送请求前通知 app 改变状态
                this.props.updateAppState({err:error.message,isLoding:false})
            }
        )
    }
    render() {
        return (
            <section className="jumbotron">
                <h3 className="jumbotron-heading">搜索用户</h3>
                <div>
                    <input ref={c => this.inputElement = c} type="text" placeholder="输入关键词点击搜索" />&nbsp;
                    <button onClick={this.search}>搜索</button>
                </div>
            </section>
        )
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

消息订阅与发布

工具库 :PubSubJS

安装 工具库 pubsub-js

npm add pubsub-js
  • 1

基本语法

# 订阅消息,第一个参数是消息名,第二参数是一个函数
const token = PubSub.subscribe('delete', function(data){ });

# 取消订阅
PubSub.unsubscribe(token)

# 发布消息
PubSub.publish('delete', data)

# 引入包组件
import PubSub from 'pubsub-js'
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

案例 :将axios中的搜索案例优化

只改动了三个组件 App、Search、List

App.js

import React, { Component } from 'react'
import List from './components/List/List'
import Search from './components/Search/Search'

export default class App extends Component {
    render() {
        return (
            <div className="container">
                <Search/>
                <List/>
            </div>
        )
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

Search.jsx

import React, { Component } from 'react'
import PubSub from 'pubsub-js'
import axios from 'axios'

export default class Search extends Component {
    // 处理键盘输入的值
    search = () => {
        // 获取用户的输入
        const { value } = this.inputElement
        // 发送请求前通知 List 组件改变状态
        // this.props.updateAppState({isFirst:false,isLoding:true})
        PubSub.publish('userList',{isFirst:false,isLoding:true})
        // 发起网络请求
        axios.get(`http://localhost:3000/api1/search/users?q=${value}`).then(
            (response) => {
                // 发送请求前通知 app 改变状态
                // this.props.updateAppState({users:response.data.items,isLoding:false})
                PubSub.publish('userList',{users:response.data.items,isLoding:false})
            },
            (error) => {
                // 发送请求前通知 app 改变状态
                // this.props.updateAppState({err:error.message,isLoding:false})
                PubSub.publish('userList',{err:error.message,isLoding:false})
            }
        )
    }
    render() {
        return (
            <section className="jumbotron">
                <h3 className="jumbotron-heading">搜索用户</h3>
                <div>
                    <input ref={c => this.inputElement = c} type="text" placeholder="输入关键词点击搜索" />&nbsp;
                    <button onClick={this.search}>搜索</button>
                </div>
            </section>
        )
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

List.jsx

import React, { Component } from 'react'
import PubSub from 'pubsub-js'
import './List.css'
export default class List extends Component {
    state = {
        users:[],// users 初始状态
        isFirst:true,// 是否为第一次打开页面
        isLoding:false,// 表示是否处于加载中
        err:''// 存储请求错误信息
    }

    componentDidMount(){
        this.token = PubSub.subscribe('userList',(msg,data)=>{
            this.setState(data)
        })
    }

    componentWillUnmount(){
        PubSub.unsubscribe(this.token)
    }

    render() {
        const {users,isFirst,isLoding,err} = this.state
        return (
            <div className="row">
                {
                    isFirst ? <h2>欢迎访问!!!</h2> : 
                    isLoding ? <h2>Loding ...... </h2> : 
                    err ? <h2>{err}</h2> : 
                    users.length===0 ? <h2>为搜索到相关用户</h2> : 
                    users.map((user) => {
                        return (
                            <div key={user.id} className="card">
                                <a rel="noreferrer" href={user.html_url} target="_blank">
                                    <img alt='head_photo' src={user.avatar_url} style={{ width: '100px' }} />
                                </a>
                                <p className="card-text">{user.login}</p>
                            </div>
                        )
                    })
                }
            </div>
        )
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

Fetch 发送请求(了解即可,兼容性不太行)

基本使用

fetch(`http://localhost:3000/api1/search/users?q=${value}`).then(
    response => {
        console.log("联系服务器成功了...")
        return response.json()
    }
).then(
    response => {
        console.log("获取数据成功了...",response)
    }
).catch(
    error => {
        console.log("出错了...",error)
    }
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

github 案例改善

Search.jsx

import React, { Component } from 'react'
import PubSub from 'pubsub-js'

export default class Search extends Component {
    // 处理键盘输入的值
    search = async() => {
        // 获取用户的输入
        const { value } = this.inputElement
        // 发送请求前通知 List 组件改变状态
        // this.props.updateAppState({isFirst:false,isLoding:true})
        PubSub.publish('userList',{isFirst:false,isLoding:true})
        // 发起网络请求(使用 fetch 发送)
        try {
            const response = await fetch(`http://localhost:3000/api1/search/users?q=${value}`)
            const data = await response.json()
            PubSub.publish('userList',{users:data.items,isLoding:false})
        } catch (error) {
            PubSub.publish('userList',{err:error.message,isLoding:false})
        }
    }
    render() {
        return (
            <section className="jumbotron">
                <h3 className="jumbotron-heading">搜索用户</h3>
                <div>
                    <input ref={c => this.inputElement = c} type="text" placeholder="输入关键词点击搜索" />&nbsp;
                    <button onClick={this.search}>搜索</button>
                </div>
            </section>
        )
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

React 路由

SPA

spa :单页面应用(single page web application),整个应用只有一个页面。点击页面的连接只会局部刷新。数据都需要通过 ajax 异步展示在前端页面。

路由

一个路由就是一个映射关系。key 为路径,value 为 function(前端 :函数) 或 一个component (后端 :组件)

前端路由基石 history 对象

//方法一,直接使用H5推出的history身上的API
let history = History.createBrowserHistory()

//方法二,hash值(锚点)
let history = History.createHashHistory()
  • 1
  • 2
  • 3
  • 4
  • 5

react-route-dom

  • 一个插件库
  • 专门用来实现一个 SPA 应用
  • 基于 react 的项目基本都会用到此库

react-route-dom 内置组件

react-route-dom 对象

  • history对象
  1. match对象
  2. withRouter函数

路由的基本使用

安装 react-route-dom 5版本

npm install react-router-dom@5.2.0
  • 1

案例—组件切换

注意 :public 文件夹中的 index.html 文件是引入了 bootstrap 样式的

index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter } from 'react-router-dom'
import App from './App'

ReactDOM.render(
    <BrowserRouter>
        <App />
    </BrowserRouter>,
    document.getElementById("root")
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
App.js
import React, { Component } from 'react'
import { Link, Route } from 'react-router-dom'
import About from './components/About/About'
import Home from './components/Home/Home'

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="/home">Home</Link>
                            <Link className="list-group-item" to="/about">About</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>
        )
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
组件 About.jsx
import React, { Component } from 'react'

export default class About extends Component {
  render() {
    return (
      <h3>我是 about 组件</h3>
    )
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
组件 Home.jsx
import React, { Component } from 'react'

export default class Home extends Component {
  render() {
    return (
        <h3>我是 Home 组件</h3>
    )
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

路由组件和一般组件区别

1、写法不同

路由组件 :<Route path="/about" component={About} />
一般组件 :<About />
  • 1
  • 2

2、存放位置不同

  • 路由组件 :放在 /src/pages 文件夹中
  • 一般组件 :放在 /src/components 文件夹中

3、接收到的 props 不同

路由组件 :接收到三个固定属性(history、location、match)
	history : 
		go : f go(n)
		goBack : f goBack()
        goForward : f goForward()
		push : f push(path,state)
        replace : f replace(path,state)
	location : 
		pathname : "/about"
		search : ""
		state : undefined
	match:
		params : {}
		path : "/about"
		url : "/about"
一般组件 :组件标签中传递了什么,就能接收到什么
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

NavLink 组件 的使用

该组件点击选中的时候会默认高亮,通过 activeClassName 属性往 className 属性上追加属性。不写该属性默认添加active 样式。

App.js

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

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 中,使用 路由链接实现切换组件--编写路由链接 */}
                            <NavLink activeClassName='active' className="list-group-item" to="/home">Home</NavLink>
                            <NavLink activeClassName='active' className="list-group-item" to="/about">About</NavLink>
                        </div>
                    </div>
                    <div className="col-xs-6">
                        <div className="panel">
                            <div className="panel-body">
                                {/* 注册路由 */}
                                <Route path="/about" component={About} />
                                <Route path="/home" component={Home} />
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        )
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

封装 NavLink 组件

一个小案例 :

针对前面 路由的基本使用的案例基础上做了一些改动(App.js和新增一个自己封装的NavLink组件

App.js

import React, { Component } from 'react'
import { NavLink, Route } from 'react-router-dom'
import About from './pages/About/About'
import Home from './pages/Home/Home'
import MyNavLink from './components/MyNavLink'

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">
                            <MyNavLink to="/about">About</MyNavLink>
                            <MyNavLink to="/home">Home</MyNavLink>
                        </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>
        )
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

MyNavLink 组件

import React, { Component } from 'react'
import { NavLink } from 'react-router-dom'
export default class MyNavLink extends Component {
  render() {
    return (
      // NavLink 组件标签中的标签体对应的属性是 children 。相当于 input 中的 value 属性
        <NavLink activeClassName='active' className="list-group-item" to="/home" {...this.props}/>
    )
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

Switch 组件的使用

  • path 和 component 是一一对应的关系
  • switch 组件可以提高路由匹配效率(单一匹配)
import React, { Component } from 'react'
import { Route, Switch } from 'react-router-dom'
import About from './pages/About/About'
import Home from './pages/Home/Home'
import MyNavLink from './components/MyNavLink/MyNavLink'

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">
                            <MyNavLink to="/about">About</MyNavLink>
                            <MyNavLink to="/home">Home</MyNavLink>
                        </div>
                    </div>
                    <div className="col-xs-6">
                        <div className="panel">
                            <div className="panel-body">
                                {/* 这样写,默认就只会匹配到一个路由,不会出现一个路径匹配多个路由的结果 */}
                                <Switch>
                                    <Route path="/about" component={About} />
                                    <Route path="/home" component={Home} />
                                    <Route path="/home" component={Test} />
                                </Switch>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        )
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

解决样式丢失问题

问题描述 :

当路由地址是多级的时候,例如 :/a/b/c ,点击完路由之后在刷新页面会出现无法加载 bootstrap 样式

在这里插入图片描述

解决办法 :

方式一 :

将 index.html 页面中样式引入路径

<link rel="stylesheet" href="./css/bootstrap.css">
  • 1

该为 :

<link rel="stylesheet" href="/css/bootstrap.css">
  • 1
方式二 :

将 index.html 页面中样式引入路径

<link rel="stylesheet" href="./css/bootstrap.css">
  • 1

该为 :

<link rel="stylesheet" href="%PUBLIC_URL%/css/bootstrap.css">
  • 1
方式三 :(不常用)

路由的方式,将 BrowserRouter 该为 HashRouter

这样修改的话,样式引入哪里可以使用 ./

因为 HashRouter 这里使用的是锚点,# 号后面的路径都不会计入总路径,在找静态资源的时候会从 # 之前的地址为当前地址开始找。

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

  • 默认使用的是模糊匹配(【输入路径】必须包含要【匹配的路径】,且顺序要一致)
  • 开启严格模式 <Route exact={true} path="/about" component={About} />
  • 严格匹配不要随便开启,需要时候在开启,有些时候开启会导致无法继续匹配二级路由

Redirect 的使用

  • 一般写在所有路由的最下方,当所有路由都无法匹配的时候,跳转至 Redirect 指定的路由

  • 具体编码

  • import { Route, Switch,Redirect  } from 'react-router-dom'
    <Switch>
        <Route path="/about" component={About} />
        <Route path="/home" component={Home} />
        <Redirect to="/about" />
    </Switch>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

嵌套路由的使用

  • 注册子路由的时候要写上父路由的 path 值
  • 路由的匹配是按照注册路由的顺序进行的

效果 :

在这里插入图片描述

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="/css/bootstrap.css">
</head>
<body>
    <div id="root"></div>
</body>
</html>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

index.js

import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter } from 'react-router-dom'
import App from './App'

ReactDOM.render(
    <BrowserRouter>
        <App />
    </BrowserRouter>,
    document.getElementById("root")
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

App.js

import React, { Component } from 'react'
import { Route, Switch,Redirect  } from 'react-router-dom'
import About from './pages/About/About'
import Home from './pages/Home/Home'
import MyNavLink from './components/MyNavLink/MyNavLink'

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">
                            <MyNavLink to="/about">About</MyNavLink>
                            <MyNavLink to="/home">Home</MyNavLink>
                        </div>
                    </div>
                    <div className="col-xs-6">
                        <div className="panel">
                            <div className="panel-body">
                                {/* 这样写,默认就只会匹配到一个路由,不会出现一个路径匹配多个路由的结果 */}
                                <Switch>
                                    <Route path="/about" component={About} />
                                    <Route path="/home" component={Home} />
                                    <Redirect to="/about" />
                                </Switch>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        )
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

About.jsx

import React, { Component } from 'react'

export default class About extends Component {
  render() {
    return (
      <h3>我是 about 组件</h3>
    )
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

Home.jsx

import React, { Component } from 'react'
import { Route, Switch,Redirect,NavLink } from 'react-router-dom'
import Message from './Message/Message'
import News from './News/News'

export default class Home extends Component {
  render() {
    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>
          <Switch>
            <Route path="/home/news" component={News} />
            <Route path="/home/message" component={Message} />
            <Redirect to="/home/news" />
          </Switch>
        </div>
      </div>
    )
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

News.jsx

import React, { Component } from 'react'

export default class News extends Component {
    render() {
        return (
            <ul>
                <li>news001</li>
                <li>news002</li>
                <li>news003</li>
            </ul>
        )
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

Message.jsx

import React, { Component } from 'react'

export default class Message extends Component {
    render() {
        return (
            <div>
                <ul>
                    <li>
                        <a href="/message1">message001</a>&nbsp;&nbsp;
                    </li>
                    <li>
                        <a href="/message2">message002</a>&nbsp;&nbsp;
                    </li>
                    <li>
                        <a href="/message/3">message003</a>&nbsp;&nbsp;
                    </li>
                </ul>
            </div>
        )
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

向路由组件传递参数

在 Message 组件中创建一个 Detail 组件

常用指数 :params > search > state

传递 params 参数

  • 路由连接(携带参数):<Link to={/home/message/detail/${obj.id}/${obj.title}}>{obj.title}
  • 注册路由(声明接收):
  • 详情组件接收参数 :const {id,title} = this.props.match.params

参数格式

在这里插入图片描述

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

export default class Message extends Component {
    state = {
        messageArr: [
            { id: 1, title: "消息1" },
            { id: 2, title: "消息2" },
            { id: 3, title: "消息3" }
        ]
    }
    render() {
        const {messageArr} = this.state
        return (
            <div>
                <ul>
                    {
                        messageArr.map((obj) => {
                            return (
                                <li key={obj.id}>
                                    {/* 向路由组件传递 params 参数 */}
                                    <Link to={`/home/message/detail/${obj.id}/${obj.title}`}>{obj.title}</Link>
                                </li>
                            )
                        })
                    }
                </ul>
                <hr/>
                {/* 声明接收 params 参数 :id 表示接收到的参数名称叫id 可以根据情况改变*/}
                <Route path="/home/message/detail/:id/:title" component={Detail} />
            </div>
        )
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
Detail.jsx
import React, { Component } from 'react'

const DetailData = [
    {id:1,content:"容器1"},
    {id:2,content:"容器2"},
    {id:3,content:"容器3"}
]
export default class Detail extends Component {
  render() {
    const {id,title} = this.props.match.params
    const findResult = DetailData.find((detailObj)=>{
        return detailObj.id === Number(id)
    })
    return (
      <ul>
        <li>ID:{id}</li>
        <li>TITLE:{title}</li>
        <li>CONTENT:{findResult.content}</li>
      </ul>
    )
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

传递 search 参数

  • 路由连接(携带参数):<Link to={/home/message/detail?id=${obj.id}&title=${obj.title}}>{obj.title}
  • 注册路由(无需声明,正常注册即可):
  • 详情组件接收参数 :const {search} = this.props.location
  • 注意:获取到的 search 是 urlencoded 编码字符串,需要借助 query-string 解析

参数格式

在这里插入图片描述

这时候可以看得出来参数不是对象的形式,而是一个字符串,这时候我们需要 query-string 库 来将参数格式化为对象

query-string库
// 将对象转为字符串
let obj = {name:"小明",age:18}
console.log(qs.stringify(obj)) // name=小明&age=18
// 将字符串转为对象
let obj2 = "name=小明&age=18";
console.log(qs.parse(obj2)) // {name:"小明",age:18}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
Message.jsx
import React, { Component } from 'react'
import { Link,Route } from 'react-router-dom'
import Detail from './Detail/Detail'

export default class Message extends Component {
    state = {
        messageArr: [
            { id: 1, title: "消息1" },
            { id: 2, title: "消息2" },
            { id: 3, title: "消息3" }
        ]
    }
    render() {
        const {messageArr} = this.state
        return (
            <div>
                <ul>
                    {
                        messageArr.map((obj) => {
                            return (
                                <li key={obj.id}>
                                    {/* 向路由组件传递 params 参数 */}
                                    {/* <Link to={`/home/message/detail/${obj.id}/${obj.title}`}>{obj.title}</Link> */}

                                    {/* 向路由组件传递 search 参数 */}
                                    <Link to={`/home/message/detail?id=${obj.id}&title=${obj.title}`}>{obj.title}</Link>
                                </li>
                            )
                        })
                    }
                </ul>
                <hr/>
                {/* 声明接收 params 参数 :id 表示接收到的参数名称叫id 可以根据情况改变*/}
                {/* <Route path="/home/message/detail/:id/:title" component={Detail} /> */}

                {/* search 参数 无需声明接收,正常注册路由即可 */}
                <Route path="/home/message/detail" component={Detail} />
            </div>
        )
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
Detail.jsx
import React, { Component } from 'react'
import qs from 'query-string'


const DetailData = [
    { id: 1, content: "容器1" },
    { id: 2, content: "容器2" },
    { id: 3, content: "容器3" }
]
export default class Detail extends Component {
    render() {
        // 接收 params 参数
        // const { id, title } = this.props.match.params

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

        const findResult = DetailData.find((detailObj) => {
            return detailObj.id === Number(id)
        })
        return (
            <ul>
                <li>ID:{id}</li>
                <li>TITLE:{title}</li>
                <li>CONTENT:{findResult.content}</li>
            </ul>
        )
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

传递 state 参数

优点是路径里不体现参数

  • 路由连接(携带参数):<Link to={{pathname:‘/home/message/detail’,state:{id:obj.id,title:obj.title}}}>{obj.title}
  • 注册路由(无需声明,正常注册即可):
  • 详情组件接收参数 :const {id,title} = this.props.location.state

参数格式 :

在这里插入图片描述

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

export default class Message extends Component {
    state = {
        messageArr: [
            { id: 1, title: "消息1" },
            { id: 2, title: "消息2" },
            { id: 3, title: "消息3" }
        ]
    }
    
    render() {
        const {messageArr} = this.state
        return (
            <div>
                <ul>
                    {
                        messageArr.map((obj) => {
                            return (
                                <li key={obj.id}>
                                    {/* 向路由组件传递 params 参数 */}
                                    {/* <Link to={`/home/message/detail/${obj.id}/${obj.title}`}>{obj.title}</Link> */}

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

                                    {/* 向路由组件传递 state 参数,此 state 和 组件里的 state 不是一个 */}
                                    <Link to={{pathname:'/home/message/detail',state:{id:obj.id,title:obj.title}}}>{obj.title}</Link>
                                </li>
                            )
                        })
                    }
                </ul>
                <hr/>
                
                {/* 声明接收 params 参数 :id 表示接收到的参数名称叫id 可以根据情况改变*/}
                {/* <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} />
                
            </div>
        )
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
Detail.jsx
import React, { Component } from 'react'


const DetailData = [
    { id: 1, content: "容器1" },
    { id: 2, content: "容器2" },
    { id: 3, content: "容器3" }
]
export default class Detail extends Component {
    render() {
        // 接收 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 === Number(id)
        })
        return (
            <ul>
                <li>ID:{id}</li>
                <li>TITLE:{title}</li>
                <li>CONTENT:{findResult.content}</li>
            </ul>
        )
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

路由模式

push

是进行压栈模式,可以一直回退,会在浏览器上留下痕迹

replace

是替换模式,不可以后退,不会再浏览器上留下痕迹

编程式路由导航

主要使用到了 history 对象中的 api

Message.jsx

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

export default class Message extends Component {
    state = {
        messageArr: [
            { id: 1, title: "消息1" },
            { id: 2, title: "消息2" },
            { id: 3, 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 })
    }

    forward = () => {
        this.props.history.goForward();
    }

    back = () => {
        this.props.history.goBack();
    }

    goTo = () => {
        // 里面的参数为步长,正数代表前进,负数代表后退
        this.props.history.go(2);
    }

    render() {
        const { messageArr } = this.state
        return (
            <div>
                <ul>
                    {
                        messageArr.map((obj) => {
                            return (
                                <li key={obj.id}>
                                    {/* 向路由组件传递 params 参数 */}
                                    {/* <Link to={`/home/message/detail/${obj.id}/${obj.title}`}>{obj.title}</Link> */}

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

                                    {/* 向路由组件传递 state 参数,此 state 和 组件里的 state 不是一个 */}
                                    <Link to={{ pathname: '/home/message/detail', state: { id: obj.id, title: obj.title } }}>{obj.title}</Link>
                                    &nbsp;<button onClick={() => { this.pushShow(obj.id, obj.title) }}>push 查看</button>
                                    &nbsp;<button onClick={() => { this.replaceShow(obj.id, obj.title) }}>replace 查看</button>
                                </li>
                            )
                        })
                    }
                </ul>
                <hr />
                {/* 声明接收 params 参数 :id 表示接收到的参数名称叫id 可以根据情况改变*/}
                {/* <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} />

                &nbsp;<button onClick={this.forward}>前进</button>
                &nbsp;<button onClick={this.back}>回退</button>
                &nbsp;<button onClick={this.goTo}>go</button>
            </div>
        )
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89

Detail.jsx

import React, { Component } from 'react'
import qs from 'query-string'

const DetailData = [
    { id: 1, content: "容器1" },
    { id: 2, content: "容器2" },
    { id: 3, content: "容器3" }
]
export default class Detail extends Component {
    render() {
        // 接收 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 === Number(id)
        })
        return (
            <ul>
                <li>ID:{id}</li>
                <li>TITLE:{title}</li>
                <li>CONTENT:{findResult.content}</li>
            </ul>
        )
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

withRouter

用于一般组件,因为一般组件中没有 路由组件的 三大属性,所以就无法使用 路由组件中的 history ,但是一般组件经过 withRouter 函数加工过之后,就拥有了 路由组件的 三大属性了。

具体使用

// 引入 withRouter 
import { withRouter } from 'react-router-dom'

// 使用 withRouter 加工一般组件
export default withRouter(Message)
  • 1
  • 2
  • 3
  • 4
  • 5

样式组件库 antd

antd 基本使用

引入依赖

npm add antd
  • 1

组件文档

https://ant.design/components/overview-cn/

简单案例

引入一个按钮组件

1、在页面组件上引入需要的样式组件

// 引入按钮组件
import { Button } from 'antd';

// 引入 图标 组件
import { DownloadOutlined } from '@ant-design/icons';

// 引入样式文件
import 'antd/dist/antd.css'
或
import 'antd/dist/antd.min.css'
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

2、使用 组件

<Button type="primary" icon={<DownloadOutlined />} >Download</Button>
  • 1

在这里插入图片描述

antd 样式的按需引入

如果我们在组件中 直接使用 import '/antd/dist/antd.css' 这种是将所有的样式都引进来了,例如上面的按钮案例,我们只需要一个按钮的样式即可,但是却把整个 antd 的所有组件样式都引进来了。

配置完之后再页面组件中就不需要在写 import '/antd/dist/antd.css‘

1、引入两个库

npm add react-app-rewired customize-cra
react-app-rewired : 启动脚手架
customize-cra : 更改脚手架默认的配置项
  • 1
  • 2
  • 3

2、修改 package.json 文件中的配置

原来的 :
"scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
},

修改后的 :
"scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test",
    "eject": "react-scripts eject"
},
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

3、引入依赖

npm add babel-plugin-import
  • 1

4、在根目录(跟 package.json 同级的目录)中创建 config-overrides.js 文件

const { override, fixBabelImports } = require('customize-cra');
module.exports = override(
    fixBabelImports('import', {
        libraryName: 'antd',
        libraryDirectory: 'es',
        style: 'css',
    }),
);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

5、将页面中的 import '/antd/dist/antd.css' 去掉,启动测试

在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/65908
推荐阅读
相关标签
  

闽ICP备14008679号