react使用脚手架搭建

  • Post author:
  • Post category:其他




一.创建项目并启动



1.1全局安装

npm install -g create-react-app



1.2 创建项目

create-react-app 这里写你的项目名



1.3进入项目文件夹

cd 步骤二写的项目名



1.4启动项目

npm start



二.脚手架文件介绍

在这里插入图片描述



三、实战



3.1一个简单的hello

  • 创建一个

    components

    文件夹,专门用于放组件,文件夹下再创建不同组件名的文件夹,组件一多就不会乱
  • 把写好的组件引入App.js中,这是最大的父组件

在这里插入图片描述

在这里插入图片描述



3.2样式的模块化

  • 为避免样式名冲突可以写,但不常用

在这里插入图片描述

在这里插入图片描述



3.3react插件的安装

  • 以后就不用手敲模板的


    rcc

    就可以出现类式组件模板,

    rfc

    就出现函数组件

在这里插入图片描述

在这里插入图片描述



3.4todoList案例

  • 功能:组件化实现增删改查
  • 对props类型限制 :

    npm install prop-types --save


APP.js

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

export default class App extends Component {
    state = {
        todoList: [
            { id: '001', name: '吃饭', check: true },
            { id: '002', name: '睡觉', check: false },
            { id: '003', name: '玩游戏', check: true },
        ]
    }
    // 添加todo
    addTodo = (todoObj) => {
        const { todoList } = this.state
        this.setState({ todoList: [todoObj, ...todoList] })
    }
    // 改变todo状态
    updateTodo = (id, check) => {
        const { todoList } = this.state
        const newTodoList = todoList.map(todoObj => {
            if (id === todoObj.id) return { ...todoObj, check }
            else return todoObj
        })
        this.setState({ todoList: newTodoList })
    }

    //删除todo
    deleteToto = (id) => {
        const { todoList } = this.state
        if (window.confirm('确定删除吗?')) this.setState({ todoList: todoList.filter(todo => todo.id !== id) })
    }

    // 切换全选反选
    handleAllTodo = (check) => {
        const { todoList } = this.state
        this.setState({
            todoList: todoList.map(todo => {
                return { ...todo, check }
            })
        })
    }

    //清除所有已选
    handleAllClear = () => {
        const { todoList } = this.state
        this.setState({
            todoList: todoList.filter(todo => {
                return !todo.check
            })
        })
    }
    render() {
        const { todoList } = this.state
        return (
            <div>
                <div className="todo-container">
                    <div className="todo-wrap">
                        <Header addTodo={this.addTodo} />
                        <List todoList={todoList} updateTodo={this.updateTodo} deleteToto={this.deleteToto} />
                        <Footer todoList={todoList} handleAllTodo={this.handleAllTodo} handleAllClear={this.handleAllClear} />
                    </div>
                </div>
            </div>
        )
    }


}




src\components\Header\index.jsx

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

export default class Hrader extends Component {
    // 对传入的props进行类型与必要性校验
    static propTypes = {
        addTodo: PropTypes.func.isRequired
    }


    handleKeyUp = (event) => {
        const { target, keyCode } = event
        //回车键才触发
        if (keyCode !== 13 || target.value.trim() === '') return
        // 组装好对象
        const obj = { id: new Date().getTime(), name: target.value, check: false }
        // 调用父方法并传值
        this.props.addTodo(obj)
        // 清空
        target.value = ''
    }
    render() {
        return (
            <div className="todo-header">
                <input onKeyUp={this.handleKeyUp} type="text" placeholder="请输入你的任务名称,按回车键确认" />
            </div>
        )
    }
}


src\components\Header\index.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);
}


src\components\List\index.jsx

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

export default class List extends Component {
    // 对传入的props类型与必要性校验
    static propTypes = {
        todoList: PropTypes.array.isRequired,
        updateTodo: PropTypes.func.isRequired,
        deleteToto: PropTypes.func.isRequired
    }


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

            </ul>
        )
    }
}


src\components\List\index.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;
  }


src\components\Item\index.jsx

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

export default class Item extends Component {
    // 对传入的props类型与必要性进行校验
    static propTypes = {
        updateTodo: PropTypes.func.isRequired
    }


    state = { mouse: false }
    handleMouse = (flag) => {
        // 必须要返回函数
        return () => {
            this.setState({ mouse: flag })
        }
    }

    // 修改选中的状态
    handleCheck = (id) => {
        return (event) => {
            this.props.updateTodo(id, event.target.checked)
        }
    }

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


src\components\Item\index.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;
  }


src\components\Footer\index.jsx

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

export default class Footer extends Component {
    // 切换全选反选
    checkAlltodo = (event) => {
        this.props.handleAllTodo(event.target.checked)
    }

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


src\components\Footer\index.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;
  }
  


效果


在这里插入图片描述



3.5脚手架配置代理


安装

npm install axios



3.5.1前置说明

  • react本身只关注界面,并不包含发送ajax请求
  • 前端应用需要通过ajax请求 与后端进行数据交互
  • react应用中需要集成第三方库或自己封装
  • 常用的第三方库为axios:
  1. 封装XmlHttpRequest对象的ajax
  2. promise风格
  3. 可以在浏览器与node服务器端使用

    但是直接请求会被同源策略拦截,需要一个代理,这个代理相当于中介,它负责转发请求与相应,也正是因为它只是转发,而没有触发ajax引擎,所以不会被拦截下来.


这里启动了node服务器做测试



建立setupProxy文件,文件名不能改

const proxy = require('http-proxy-middleware')//引入http-proxy-middleware,react脚手架已经安装

module.exports = function (app) {
    app.use(
        proxy.createProxyMiddleware('/api1', { //遇见/api1前缀的请求,就会触发该代理配置
            target: 'http://localhost:5000', //请求转发给谁
            changeOrigin: true,//控制服务器收到的请求头中Host的值
            pathRewrite: { '^/api1': '' } //重写请求路径,下面有示例解释
        }),
        proxy.createProxyMiddleware('/api2', {
            target: 'http://localhost:5001',
            changeOrigin: true,
            pathRewrite: { '^/api2': '' }
        }),
    )
}


APP.js

import axios from 'axios';
import { Component } from 'react';
import './App.css';

export default class App extends Component {
    getStudent = () => {
        axios.get("http://localhost:3000/api1/students").then(res => {
            console.log(res)
        })
    }
    getCar = () => {
        axios.get("http://localhost:3000/api2/cars").then(res => {
            console.log(res)
        })
    }
    render() {
        return (
            <div>
                <button onClick={this.getStudent}>点我获取学生数据</button>
                <button onClick={this.getCar}>点我获取汽车数据</button>
            </div>
        )
    }


}




效果


在这里插入图片描述



3.5.2GitHub搜索案例



消息订阅发布模式

npm install pubsub-js


src\App.js

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

export default class App extends Component {
    render() {
        return (
            <div className="container">
                <Search />
                <List />
            </div>
        )
    }
}


代理:src\setupProxy.js

const proxy = require('http-proxy-middleware')//引入http-proxy-middleware,react脚手架已经安装

module.exports = function (app) {
    app.use(
        proxy.createProxyMiddleware('/api1', { //遇见/api1前缀的请求,就会触发该代理配置
            target: 'http://localhost:5000', //请求转发给谁
            changeOrigin: true,//控制服务器收到的请求头中Host的值
            pathRewrite: { '^/api1': '' } //重写请求路径,下面有示例解释
        })
    )
}


src\components\Search\index.js

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

export default class Search extends Component {
    searchList = () => {
        PubSub.publish('updateAppState', { isFirst: false, isLoading: true });
        const { value: keywork } = this.keyWorkEl
        axios.get(`/api1/search/users?q=${keywork}`).then((res) => {
            PubSub.publish('updateAppState', { isLoading: false, users: res.data.items });
        }).catch((err) => {
            PubSub.publish('updateAppState', { isLoading: false, err: err.message });
        })
    }
    render() {
        return (
            <section className="jumbotron">
                <h3 className="jumbotron-heading">搜索用户</h3>
                <div>
                    <input ref={c => this.keyWorkEl = c} type="text" placeholder="输入关键词" />&nbsp;
                    <button onClick={this.searchList}>搜索</button>
                </div>
            </section>
        )
    }
}


src\components\list\index.js

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

export default class List extends Component {
    state = ({
        users: [],
        isFirst: true,
        isLoading: false,
        err: ''
    })
    componentDidMount() {
        // 订阅
        this.token = PubSub.subscribe('updateAppState', (msg, dataObj) => {
            this.setState(dataObj)
        });
    }
    componentWillUnmount() {
        //取消订阅
        PubSub.unsubscribe(this.token)
    }
    render() {
        const { users, isFirst, isLoading, err } = this.state
        return (
            <div className="row">
                {
                    isFirst ? <h2>欢迎使用,请输入关键字,随后搜索</h2> :
                        isLoading ? <h2>请稍后</h2> :
                            err ? <h2>err</h2> :
                                users.map(user => {
                                    return (
                                        <div className="card" key={user.id}>
                                            <a href={user.html_url} target="_blank">
                                                <img src={user.avatar_url} style={{ width: '100px' }} />
                                            </a>
                                            <p className="card-text">{user.login}</p>
                                        </div>
                                    )
                                })}
            </div>
        )
    }
}


效果


会有默认显示,查询时有loading,点击可跳转打开用户界面

在这里插入图片描述



3.5.3fetch发送请求

  • 非第三方库不用下载,直接window自带,主要提倡关注分离,不是xhr
  • 兼容性不高,有一些老版本浏览器不支持


根据3.5的代码改造

 searchList = async () => {
        PubSub.publish('updateAppState', { isFirst: false, isLoading: true });
        const { value: keywork } = this.keyWorkEl
        // axios.get(`/api1/search/users?q=${keywork}`).then((res) => {
        //     PubSub.publish('updateAppState', { isLoading: false, users: res.data.items });
        // }).catch((err) => {
        //     PubSub.publish('updateAppState', { isLoading: false, err: err.message });
        // })
        try {
            const res = await fetch(`/api1/search/users?q=${keywork}`)
            const data = await res.json()
            PubSub.publish('updateAppState', { isLoading: false, users: data.items });
        } catch (err) {
            PubSub.publish('updateAppState', { isLoading: false, err: err.message });
        }
    }



3.6路由的使用

  • 点击导航链接引起路径改变,被路由器监听到进行匹配组件从而展示
  • 因为需要在同一个路由里面跳转,所以BrowserRouter直接包裹在最外层就好
  • 路由组件是通过路由匹配成功展示的组件,一般组件就是不受路由影响的组件
  • 路由组件props默认会有路由数据,一般组件没有
npm install react-router-dom


src\index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { BrowserRouter } from 'react-router-dom'

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <BrowserRouter>
    <React.StrictMode>
      <App />
    </React.StrictMode>
  </BrowserRouter>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();


src\App.js

import React, { Component } from 'react'
import { Link, Route } from 'react-router-dom'
import About from './components/About'
import Home from './components/Home'
import './App.css'

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">
                            <Link className="list-group-item active" 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>
        )
    }
}


两个子组件

在这里插入图片描述


效果


在这里插入图片描述



3.6.1NavLink

  • 选中状态默认添加了active类名,使用activeClassName指定样式类名,

    v6改用className使用用函数返回

在这里插入图片描述



3.6.2封装NavLink组件

  • 组件之间写的是特殊属性叫标签体,传递给子组件的时候会默认叫children的key

在这里插入图片描述

src\components\MyNavLink.jsx

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='red' className="list-group-item" {...this.props} />
        )
    }
}

在这里插入图片描述



3.6.3Switch组件(v6移除)

  • 如果路径相同但含有多个组件的情况,会将多个组件同时展示,如果不想这么做,就在外面包裹一层,那么匹配上第一个以后就不会往下走

在这里插入图片描述



3.6.4路由的模糊匹配与严格匹配(v6版没这概念)

  • 路由的匹配规则是如图,会将/about/a/b提取成about a b,然后依次匹配,并且是有顺序地匹配,模糊匹配情况下,只要前面字段匹配上了就会展示,如果添加严格匹配则需要about a b完全一样才展示

在这里插入图片描述

  • 添加严格模式,exact

在这里插入图片描述



3.6.5嵌套路由

  • 如下面代码所示,News和Message组件都是嵌套在Home组件下面的,路由有个匹配规则,像Home组件About组件这两个都是先注册的路由,先注册先匹配,如果匹配不到就不进行下一步.所以假设News的路由只写/news,那么在模糊匹配的时候发现没有news开头的,就不会继续进行.
  • 所以子路由需要在前面加上上一级路由的path值


src\App.js

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

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/a/b" a={1} b={2}>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} />
                                </Switch>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        )
    }
}


src\pages\Home\index.jsx

import React, { Component } from 'react'
import News from './News'
import Message from './Message'
import MyNavLink from '../../components/MyNavLink'
import { Route } from 'react-router-dom'

export default class Home extends Component {
    render() {
        return (
            <div>
                <div>我是home</div>
                <div>
                    <ul className="nav nav-tabs">
                        <li>
                            <MyNavLink to="/home/news">News</MyNavLink>
                        </li>
                        <li>
                            <MyNavLink to="/home/message">Message</MyNavLink>
                        </li>
                    </ul>
                    <div>
                        <Route path='/home/news' component={News} />
                        <Route path='/home/message' component={Message} />
                    </div>
                </div>
            </div>
        )
    }
}


效果


在这里插入图片描述



3.6.6向路由传递params参数

  • 子组件可以通过this.props接收

在这里插入图片描述



3.6.7向路由组件传递state参数

  • 这种传参不会在地址栏暴露,使用的时候因为是在操作history,所以刷新也没问题,但是复制链接给别人再打开就有问题了
  • 从this.props取值

在这里插入图片描述



3.6.8replace

  • 关掉当前页,打开新页面
  <MyNavLink replace to="/home/news">News</MyNavLink>



3.6.9编程式路由导航

在这里插入图片描述



3.6.10withRouter的使用(v6已移除)

  • 能够将一般组件身上加上路由组件所特有的api,也就是本来一般组件没有this.props.history,但是引入withRouter后就有了
  • withRouter的返回值是一个新组件

在这里插入图片描述



3.6.11 BrowserRouter与HashRouter的区别

  • 1.底层原理不一样

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

    HashRouter使用的是URL的哈希值
  • 2.url变现形式不一样

    BrowserRouter的路径中没有#

    HashRouter的路径包含#
  • 3.刷新后对路由state参数的影响

    BrowserRouter没有任何影响,因为state保存在history对象中

    HashRouter刷新后会导致路由state参数的丢失



3.7 路由V6版本

  • React Router以三个不同的包发布到npm,它们分别为:

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

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

    <BrowserRouter>


    3.react-router-native:包含react-router所有内容,并添加一些专门用于ReactNative的API,例如:

    <NativeRouter>
  • 与React Router5x版本相比,改变了什么?

    1.内置组件的变化:移除

    <Switch>

    ,新增

    <Routes>



    2.语法的变化:

    component={About}变为

    element={


    <About/>

    }等

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

    4.没有withRouter

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



3.7.1一级组件

  • Routes改为必写,且有switch匹配成功就不往下走的功能,语法的变化:

    component={About}变为

    element={


    <About/>

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

export default function App() {
    return (
        <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">
                            <div>
                                <Routes>
                                    <Route path='/about' element={<About />}></Route>
                                    <Route path='/home' element={<Home />}></Route>
                                </Routes>
                            </div>
                        </div>
                    </div>
                </div>
            </div></div>
    )
}

在这里插入图片描述



3.7.2重定向

  • navigate只要被渲染就会修改路径,引起页面切换
  • replace属性用于控制跳转模式,true就删掉当前页,跳转新页面


src\App.js

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

export default function App() {
    return (
        <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">
                            <div>
                                <Routes>
                                    <Route path='/about' element={<About />}></Route>
                                    <Route path='/home' element={<Home />}></Route>
                                    <Route path='/' element={<Navigate to="/about" />}></Route>
                                </Routes>
                            </div>
                        </div>
                    </div>
                </div>
            </div></div>
    )
}


src\pages\Home\index.jsx

import React, { useState } from 'react'
import { Navigate } from 'react-router-dom'

export default function Home() {
    const [sum, setSum] = useState(1)
    return (
        <div>
            {sum === 2 ? <Navigate to="/about" replace={false} /> : <h4>当前sum的值是:{sum}</h4>}
            <h2>Home组件内容</h2>
            <button onClick={() => setSum(2)}>点我sum变2</button>
        </div>
    )
}

在这里插入图片描述



3.7.3NavRoute选中状态

  • v5是使用activeClassName,而v6是使用className函数返回值
import React from 'react'
import { NavLink, Route, Routes, Navigate } from 'react-router-dom'
import About from './pages/About'
import Home from './pages/Home'
import './App.css'

export default function App() {
    function computedClassName({isActive}){
        return isActive ? "list-group-item active" : "list-group-item"
    }
    return (
        <div>
            <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={computedClassName} to="/about" >About</NavLink>
                            <NavLink className={computedClassName} 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></div>
    )
}

在这里插入图片描述



3.7.4useRoutes路由表

  • 跟vue很像,就是根据path跟element生成route结构


src\App.js

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

export default function App() {
    // 新增
    const element = useRoutes(router)
    function computedClassName({ isActive }) {
        return isActive ? "list-group-item active" : "list-group-item"
    }
    return (
        <div>
            <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={computedClassName} to="/about" >About</NavLink>
                            <NavLink className={computedClassName} 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> */}
                                {/* 修改成 */}
                                {element}
                            </div>
                        </div>
                    </div>
                </div>
            </div></div>
    )
}


src\router\index.js


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

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

在这里插入图片描述



3.7.5嵌套路由

  • Outlet类似vue的router-view,子路由用children,如果想子组件选项高亮,而父组件不需要高亮,那就在父组件NavLink中加end


src\router\index.js


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

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


src\pages\Home\index.jsx

import React from 'react'
import '../../App.css'
import { Outlet, NavLink } from 'react-router-dom'

export default function Home() {
  function computedClassName({ isActive }) {
    return isActive ? "list-group-item active" : "list-group-item"
  }
  return (
    <div>
      <div className="list-group">
        <NavLink className={computedClassName} to="News" >News</NavLink>
        <NavLink className={computedClassName} to="Message" >Message</NavLink>
      </div>
      <Outlet />
    </div>
  )
}

在这里插入图片描述



3.7.6路由传参parmas

  • 传参

在这里插入图片描述

在这里插入图片描述

  • 接收参数

在这里插入图片描述



3.7.7路由传参search

  • 传参

在这里插入图片描述

  • 拿参

在这里插入图片描述



3.7.8路由传参state

  • 传参

在这里插入图片描述

  • 取参

    在这里插入图片描述



3.7.9编程式路由导航useNavigate

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

export default function Home() {
    const navigate = useNavigate()
    function goAbout() {
        // useNavigate是一个函数,参数1为路径,search跟parmas的可以直接拼在参数1的路径后面
        // state就在参数2中定义
        navigate('/about', {
            replace: false,
            state: { id: 99 }
        })

        // 前进
        // navigate(1)
        // 后退
        // navigate(-1)
    }
    return (
        <div>
            <h2>Home组件内容</h2>
            <button onClick={goAbout}>去about</button>
        </div>
    )
}



3.7.10 useInRouterContent

  • 作用:如果组件在

    <Router>

    的上下文中呈现,则useInRouterContent钩子返回true,否则返回false
  • 我的理解就是有没有被BrowserRouter包裹住
  • 使用:useInRouterContext()就行



3.8redux(下面还有官方的react-redux,这里用于方便理解)


  • 英文官网

  • 中文文档


    redux是一个专门用于做状态管理的js库(不是react插件库)

    它可以用在react、angel和vue等,但基本与react配合使用

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

    跟vuex的作用差不多,也可以用订阅发布,但是如果多个组件都需要用到,得向每个组件都定义发送,每个组件都得定义接收,太多
  • 什么情况下需要使用redux?

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

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

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

在这里插入图片描述

  • Action Creators是函数,接收一个对象,type是类型是字符串,data是动作,包装好以后扔给store.action分同步和异步,同步action为Object,异步action为function。
  • store分发任务给reducers,并且带上之前的数据
  • reducers有初始化跟加工数据的作用,第一次接收时previousState是undefined,下次就是之前的数据了,加工好以后传回给store
  • 类型客人(React Components)点菜, 服务员(Action Creators)将客人的需求生成函数,例如type:点菜data番茄炒蛋然后发给老板(store),老板将这个客人之前的点菜记录跟现在的传给厨房(Redues),厨房炒好以后端给老板,客人要拿的时候去老板那里拿


下载

npm install redux


下载这个中间件是用于处理action异步,让store辨别是函数并调用

npm install redux-thunk
  • store.getState()获取仓库中的数据
  • store.dispatch用于改变数据
  • 最后store.subscribe(),需要监听数据改变,手动渲染数据,因为redux不帮渲染


src\App.js

import React from 'react'
import Count from './components/Count'

export default function App() {
    return (
        <div>
            <Count />
        </div>
    )
}


src\components\Count.jsx

import React, { useRef, useState } from 'react'
import state from '../redux/state'
import { createIncrementAction, createDecrementAction, createIncrementAsyncAction } from '../redux/count_action'

export default function Count() {
    const selectNumber = useRef(null);
    const [count, setCount] = useState(0)
    function increment() {
        const { current: { value } } = selectNumber
        state.dispatch(createIncrementAction(Number(value)))
        setCount()//手动渲染,将就写,不用跟着
    }
    function decrement() {
        const { current: { value } } = selectNumber
        state.dispatch(createDecrementAction(Number(value)))
        setCount()//手动渲染,将就写,不用跟着
    }
    function incrementIfOdd() {
        const { current: { value } } = selectNumber
        if (state.getState() % 2 !== 0) {
            state.dispatch(createIncrementAction(Number(value)))
            setCount()//手动渲染,将就写,不用跟着
        }
    }
    function incrementAsync() {
        const { current: { value } } = selectNumber
        state.dispatch(createIncrementAsyncAction(Number(value), 500))
        setCount()//手动渲染,将就写,不用跟着
    }
    return (
        <div>
            <h2>当前求和为:{state.getState()}</h2>
            <select ref={selectNumber}>
                <option value="1">1</option>
                <option value="2">2</option>
                <option value="3">3</option>
            </select>
            <button onClick={increment}>+</button>
            <button onClick={decrement}>-</button>
            <button onClick={incrementIfOdd}>当前求和为奇数再加</button>
            <button onClick={incrementAsync}>异步加</button>
        </div>
    )
}


src\redux\state.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))


src\redux\constant.js

/**
 * 该模块是用于定义action对象中type类型的常量值
 */
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'


src\redux\count_action.js

/**
 * 该文件专门为count组件生成action对象
 */

// 同步action,就是指action的值为Object类型的一般对象
import { INCREMENT, DECREMENT } from './constant'
export const createIncrementAction = data => ({ type: INCREMENT, data })
export const createDecrementAction = data => ({ type: DECREMENT, data })
// 异步action,就是指action的值为函数
export const createIncrementAsyncAction = (data, time) => {
    return (dispatch) => {
        setTimeout(() => {
            dispatch(createIncrementAction(data))
        }, time)
    }
}


src\redux\count_reducer.js

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

export default function countReducer(preState = 0, action) {
    // 从action对象中获取type,data
    const { type, data } = action
    // 根据type决定如何加工数据
    switch (type) {
        case INCREMENT://如果是加
            return preState + data
        case DECREMENT://如果是减
            return preState - data
        default:
            return preState
    }
}

在这里插入图片描述



3.9react-redux


下载

npm install react-redex

在这里插入图片描述



3.9.1 链接容器组件与UI组件(接着3.8的代码继续改造)

在这里插入图片描述


src\App.js

import React from 'react'
//引入包裹着UI组件库的容器组件
import Count from './containers/Count'
import store from './redux/state'

export default function App() {
    return (
        <div>
            {/* redux与容器组件的连接靠props */}
            <Count store={store} />
        </div>
    )
}


src\containers\Count\index.jsx

// 引入Count的UI组件
import CountUI from '../../components/Count'
// 引入connect连接UI组件库
import { connect } from 'react-redux'
// 使用connect()()创建并暴露一个count的容器组件
export default connect()(CountUI)



3.9.2基本使用

  • 1.明确两个概念:

    UI组件:不能使用任何redux的api,只负责页面的呈现、交互等

    容器组件:负责和redux通信,将结果交给UI组件
  • 2.如何创建一个容器组件—–靠react-redux的connect函数

    connect(mapStateToProps,mapDisptchToProps)(UIzujan)

    mapStateToProps映射状态,返回值是一个对象

    mapDispatchToProps:映射操作状态的方法,返回值是一个对象

    容器组件有检测 状态改变并渲染的能力
    1. 备注:容器组件中的store是靠props传进去的,而不是在容器组件中直接引入

在这里插入图片描述


src\index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { BrowserRouter } from 'react-router-dom';
import store from './redux/state'
import { Provider } from 'react-redux';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  // 此处需要用Provider包裹App,目的是让App所有的后代容器组件都能接收到store
  <Provider store={store}>
    <BrowserRouter>
      <React.StrictMode>
        <App />
      </React.StrictMode>
    </BrowserRouter>
  </Provider>
);
reportWebVitals();


src\App.js

import React from 'react'
//引入包裹着UI组件库的容器组件
import Count from './containers/Count'
import Person from './containers/Person'

export default function App() {
    return (
        <div>
            <Count />
            <hr />
            <Person />
        </div >
    )
}



3.9.2.1state


src\redux\state.js

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

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


src\redux\constant.js

—非必须

/**
 * 该模块是用于定义action对象中type类型的常量值
 */
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
export const ADD_PERSON = 'add_person'



3.9.2.2action


src\redux\action\count.js

/**
 * 该文件专门为count组件生成action对象
 */

// 同步action,就是指action的值为Object类型的一般对象
import { INCREMENT, DECREMENT } from '../constant'
export const increment = data => ({ type: INCREMENT, data })
export const decrement = data => ({ type: DECREMENT, data })
// 异步action,就是指action的值为函数
export const incrementAsync = (data, time) => {
    return (dispatch) => {
        setTimeout(() => {
            dispatch(increment(data))
        }, time)
    }
}


src\redux\action\person.js

import { ADD_PERSON } from '../constant'
export const addPerson = data => ({ type: ADD_PERSON, data })



3.9.2.3reducer


src\redux\reducer\index.js


import { combineReducers } from 'redux'
// 引入为count组件服务的reducer
import count from './count'
// person
import person from './person'
// 集合reducer,合并后总状态是一个对象
const allReducer = combineReducers({
    count,
    person
})

export default allReducer


src\redux\reducer\count.js

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

export default function count(preState = 0, action) {
    // 从action对象中获取type,data
    const { type, data } = action
    // 根据type决定如何加工数据
    switch (type) {
        case INCREMENT://如果是加
            return preState + data
        case DECREMENT://如果是减
            return preState - data
        default:
            return preState
    }
}


src\redux\reducer\person.js

import { ADD_PERSON } from '../constant'
const initPerson = [{ id: '001', name: '杀生丸', age: '18' }]
export default function person(preState = initPerson, action) {
    const { type, data } = action
    switch (type) {
        case ADD_PERSON:
            // 错误示范:
            // 是因为redux做了浅比较,发现地址值一样的,就不更新了
            // return preState.unshift(data)
            // 正确:
            // 返回新数组
            // 并且redux是要一个纯函数,纯函数:只要是同样的输入(实参),必定得到同样的输出(返回)
            // 必须遵守以下一些约束:
            // 1.不得改写参数数据
            // 2.不会产生任何副作用,例如网络请求,输入和输出设备
            // 3.不能调用Date.now()或randow等不纯的方法
            return [data, ...preState]
        default:
            return preState
    }
}

在这里插入图片描述



3.10项目打包运行

npm run build



3.11扩展



3.11.1setState()

  • setState()是同步的,但是state之后的操作是异步的
  • setState更新状态的2种写法

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

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

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

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

    updater为返回stateChange对象的函数

    updater可以 接收到state和props

    callback是可选的回调函数,它在状态更新、界面也更新后(render调用后)才被调用
  • 总结:

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

    2.使用原则:

    如果状态不依赖于原状态?使用对象方式:函数式

    如果需要在setState()执行后获取最新的状态数据,要在第二个callback函数中读取


对象式

import React, { Component } from 'react'

export default class App extends Component {
    state = { count: 0 }
    add = () => {
        const { count } = this.state
        this.setState({ count: count + 1 }, () => {
            console.log(this.state.count, '执行完渲染后回调')
        })
        console.log(this.state.count, '直接调用')
    }
    render() {
        return (
            <div>
                <h1>:{this.state.count}</h1>
                <button onClick={this.add}></button>
            </div >
        )
    }
}

在这里插入图片描述



3.11.2函数式

import React, { Component } from 'react'

export default class App extends Component {
    state = { count: 0 }
    add = () => {
        this.setState((state, props) => {
            return {
                count: state.count + 1
            }
        }, () => {
            console.log(this.state.count, '执行完渲染后回调')
        })
        console.log(this.state.count, '直接调用')
    }
    render() {
        return (
            <div>
                <h1>:{this.state.count}</h1>
                <button onClick={this.add}></button>
            </div >
        )
    }
}



3.11.3lazyLoad路由懒加载

在这里插入图片描述



3.11.4PureComponent()

  • 重写了shouldComponentUpdate(),只有state或props数据变化才返回true
  • 注意:只进行state和props数据的浅比较,如果只是数据对象内部数据变了,返回false



3.11.4 插槽

import React, { useState } from 'react'

export default function App() {
    return (
        <div>
            App
            <A render={(num) => <B num={num} />} />
        </div>
    )
}

function A(props) {
    const [num, setNum] = useState(9)
    return (
        <div>
            A
            {props.render(num)}
        </div>
    )
}

function B(props) {
    return (
        <div>B{props.num}</div>
    )
}

在这里插入图片描述



3.12hook

  • Hook是react16.8版本增加的新特性/新语法
  • 可以在函数组件中使用state以及其他的React特性



3.1.2.1State Hook

  • State Hook让函数组件也可以有state状态,并进行状态数据的读写操作
  • 语法:const [xxx,setXxx]=React.useState(init)
  • useState()说明:

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

    返回值:包含2个元素的数组,第1个为内部当前状态值,第2个为更新状态值的函数
  • setXxx()2种写法:

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

    setXxx(value=>newValue):参数为函数,接收原本的状态值,返回新的状态值,内部用其覆盖原来的状态值
import React, { useState } from 'react'

export default function App() {
    // 本质上渲染时1+NaN(初始值,每次渲染都要调用),但是以下部门react做了缓存,不会重置
    const [count, setCount] = useState(99)
    function addCount() {
        // 方法一
        // setCount(count + 1)
        // 方法二
        setCount(oldCount => oldCount + 1)
    }
    return (
        <div>
            <h2>总数为{count}</h2>
            <button onClick={addCount}>点我</button>
        </div>
    )
}

在这里插入图片描述



3.1.2.2Effect Hook

  • 可以在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
  • 语法和说明:看代码
import React, { useState, useEffect } from 'react'

export default function App() {
    const [count, setCount] = useState(99)
    const [suibain, setSuibian] = useState(99)
    function addCount() {
        setCount(count + 1)
    }

    // 如果不传第二个参数,则监听所有数据,一旦有数据变更都会改变
    // useEffect(() => {
    //     console.log('你点一下按钮')
    // })

    // 如果传空数组则什么都不监视
    // useEffect(() => {
    //     console.log('你点一下按钮')
    // }, [])

    // 数组传入值后除了初始外,只有该值变化才会触发
    // useEffect(() => {
    //     console.log('你点一下按钮')
    // }, [suibain])

    useEffect(() => {
        // 这里相当于componentDidMount和componentDidUpdate
        console.log('开启定时器')
        const timer = setInterval(() => {
            console.log('进入定时器')
            setCount(count => count + 1)
        }, 1000)
        return () => {
            // 这里相当于componentWillUnmount
            console.log('清除定时器')
            clearInterval(timer)
        }
    }, [])
    return (
        <div>
            <h2>总数为{count}</h2>
            <button onClick={addCount}>点我</button>
        </div>
    )
}

在这里插入图片描述



3.1.2.3 ref

import React, { useRef } from 'react'

export default function App() {
    const num = useRef()
    function show() {
        console.log(num.current.value)
    }

    return (
        <div>
            <input ref={num}></input>
            <button onClick={show}>点我</button>
        </div>
    )
}

在这里插入图片描述



3.1.2.4useContext跨组件传值

  • 官网例子
import React, { useRef, useContext } from 'react'

const themes = {
    light: {
        foreground: "#000000",
        background: "#eeeeee"
    },
    dark: {
        foreground: "#ffffff",
        background: "#222222"
    }
};

const ThemeContext = React.createContext(themes.dark);

export default function App() {
    return (
        <ThemeContext.Provider value={themes.dark}>
            <Toolbar />
        </ThemeContext.Provider>
    );
}

function Toolbar(props) {
    return (
        <div>
            <ThemedButton />
        </div>
    );
}

function ThemedButton() {
    const theme = useContext(ThemeContext);
    console.log(theme, 99)
    return (
        <button style={{ background: theme.background, color: theme.foreground }}>
            I am styled by theme context!
        </button>
    );
}

在这里插入图片描述



3.13 组件通信方式总结


组件间的关系:

  • 父子组件
  • 兄弟组件(非嵌套组件)
  • 祖孙组件(跨级组件)


几种通信方式:

1.props:

children props

render props

2.消息订阅-发布

pubs-sub

3.集中式管理:

redux

4.context:

生产者-消费者模式


比较好的搭配方式:

  • 父子组件:props
  • 兄弟组件:消息订阅-发布、集中式管理
  • 祖孙组件(跨级组件):消息订阅-发布、集中式管理、context(开发用的少,封装插件用的多)



版权声明:本文为weixin_47886687原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。