React学习笔记(二):React脚手架、React路由

3、React应用(基于React脚手架)

1)、使用create-react-app创建react应用

1)react脚手架

react提供了一个用于创建react项目的脚手架库:create-react-app

项目的整体技术架构为:react+webpack+es6+eslint

2)创建项目并启动

第一步,全局安装:npm i -g create-react-app

第二步,切换到想创项目的目录,使用命令:create-react-app hello-react

第三步,进入项目文件夹:cd hello-react

第四步,启动项目:npm start

3)react脚手架项目结构

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库的支持)

2)、todoList案例相关知识点

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

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

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

3)关于父子之间通信:

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

父组件

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

//创建并暴露App组件
export default class App extends Component {
    //初始化状态
    state = {
        todos: [
            { id: '001', name: '吃饭', done: true },
            { id: '002', name: '睡觉', done: true },
            { id: '003', name: '打代码', done: false }
        ]
    }

    addTodo = (todoObj) => {
        const { todos } = this.state
        const newTodos = [todoObj, ...todos]
        this.setState({ todos: newTodos })
    }

    updateTodo = (id, done) => {
        const { todos } = this.state
        const newTodos = todos.map(todoObj => {
            if (todoObj.id === id) {
                return { ...todoObj, done: done }
            } else {
                return todoObj
            }
        })
        this.setState({ todos: newTodos })
    }

    deleteTodo = (id) => {
        const { todos } = this.state
        const newTodos = todos.filter((todoObj) => {
            return todoObj.id !== id
        })
        this.setState({ todos: newTodos })
    }

    checkAllTodo = (done) => {
        const { todos } = this.state
        const newTodos = todos.map((todoObj) => {
            return { ...todoObj, done: done }
        })
        this.setState({ todos: newTodos })
    }

    clearAllDone = () => {
        const { todos } = this.state
        const newTodos = todos.filter((todoObj) => {
            return !todoObj.done
        })
        this.setState({ todos: newTodos })
    }

    render() {
        const { todos } = this.state
        return (
            
); } }

Header子组件

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

export default class Header extends Component {
    static propTypes = {
        addTodo: PropTypes.func.isRequired
    }

    handleKeyUp = (event) => {
        const { target, keyCode } = event
        const { addTodo } = this.props
        if (keyCode !== 13) {
            return
        }
        if (target.value.trim() === '') {
            alert('输入不能为空')
            return
        }
        const todoObj = { id: nanoid(), name: target.value, done: false }
        addTodo(todoObj)
        target.value = ''
    }

    render() {
        return (
            
) } }

List子组件

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

export default class List extends Component {
    static propTypes = {
        todos: PropTypes.array.isRequired,
        updateTodo: PropTypes.func.isRequired,
        deleteTodo: PropTypes.func.isRequired
    }

    render() {
        const { todos, updateTodo, deleteTodo } = this.props

        return (
            
    { todos.map(todo => { return }) }
) } }

Item子组件

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

export default class Item extends Component {
    state = {
        mouse: false
    }

    handleMouse = (flag) => {
        return () => {
            this.setState({ mouse: flag })
        }
    }

    handleCheck = (id) => {
        const { updateTodo } = this.props
        return (event) => {
            updateTodo(id, event.target.checked)
        }
    }

    handleDelete = (id) => {
        const { deleteTodo } = this.props
        if (window.confirm('确定删除吗?')) {
            deleteTodo(id)
        }
    }

    render() {
        const { id, name, done } = this.props
        const { mouse } = this.state

        return (
            
  • ) } }

    Footer子组件

    import React, { Component } from 'react'
    import PropTypes from 'prop-types'
    import './index.css'
    
    export default class Footer extends Component {
        static propTypes = {
            todos: PropTypes.array.isRequired,
            checkAllTodo: PropTypes.func.isRequired,
            clearAllDone: PropTypes.func.isRequired
        }
    
        handleCheckAll = (event) => {
            const { checkAllTodo } = this.props
            checkAllTodo(event.target.checked)
        }
    
        handleClearAllDone = () => {
            const { clearAllDone } = this.props
            clearAllDone()
        }
    
        render() {
            const { todos } = this.props
            const doneCount = todos.reduce((pre, todo) => { return pre + (todo.done ? 1 : 0) }, 0)
            const total = todos.length
    
            return (
                
    已完成{doneCount} / 全部{total}
    ) } }

    4)注意defaultChecked和checked的区别,类似的还有:defaultValue和value

    5)状态在哪里,操作状态的方法就在哪里

    4、React路由

    1)、相关理解

    1)SPA的理解
    • 单页Web应用(single page web application,SPA)
    • 整个应用只有一个完整的页面
    • 点击页面中的链接不会刷新页面,只会做页面的局部更新
    • 数据都需要通过ajax请求获取,并在前端异步展现
    2)路由的理解

    什么是路由

    • 一个路由就是一个映射关系(key:value)
    • key为路径,value可能是function或component

    路由分类

    a.后端路由

    理解:value是function,用来处理客户端提交的请求

    注册路由:router.get(path, function(req, res))

    工作过程:当node接收到一个请求时,根据请求路径找到匹配的路由,调用路由中的函数来处理请求,返回响应数据

    b.前端路由

    浏览器端路由,value是component,用于展示页面内容

    注册路由:

    工作过程:当浏览器的path变为/test时,当前路由组件就会变为Test组件

    2)、基本路由使用

    1)引入react-router-dom

    yarn add react-router-dom

    2)路由的基本使用
    1. 明确好界面中的导航区、展示区
    2. 导航区的a标签改为Link标签Demo
    3. 展示区写Route标签进行路径的匹配
    4. 的最外侧包裹了一个
    3)代码实现

    App.jsx

    import React, { Component } from 'react'
    import { Link, Route } from 'react-router-dom'
    import Home from './pages/Home/index'
    import About from './pages/About/index'
    
    export default class App extends Component {
        render() {
            return (
                

    React Router Demo

    {/* 编写路由链接 */} About Home
    {/* 注册路由 */}
    ) } }

    index.js

    import React from 'react'
    import ReactDOM from 'react-dom'
    import { BrowserRouter } from 'react-router-dom'
    import App from './App'
    
    ReactDOM.render(
        ,
        document.getElementById('root')
    )
    

    3)、路由组件与一般组件

    1)写法不同

    一般组件:

    路由组件:

    2)存放位置不同

    一般组件:components

    路由组件:pages

    接收到的props不同:

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

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

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

    4)、NavLink与封装NavLink

    1. NavLink可以实现路由链接的高亮,通过activeClassName指定样式名
    2. 标签体内容是一个特殊的标签属性
    3. 通过this.props.children可以获取标签体内容
    import React, { Component } from 'react'
    import PropTypes from 'prop-types'
    import { NavLink } from 'react-router-dom'
    import './index.css'
    
    export default class MyNavLink extends Component {
        static propTypes = {
            to: PropTypes.string.isRequired,
            children: PropTypes.string.isRequired
        }
    
        render() {
            return (
                
            )
        }
    }
    

    App.jsx

    import React, { Component } from 'react'
    import { Route } from 'react-router-dom'
    import MyNavLink from './components/MyNavLink/index'
    import Home from './pages/Home/index'
    import About from './pages/About/index'
    
    export default class App extends Component {
        render() {
            return (
                

    React Router Demo

    {/* 编写路由链接 */} Home About
    {/* 注册路由 */}
    ) } }

    5)、Switch的使用

    1. 通常情况下,path和component是一一对应的关系
    2. Switch可以提高路由匹配效率(单一匹配)

    App.jsx

    import React, { Component } from 'react'
    import { Route, Switch } from 'react-router-dom'
    import MyNavLink from './components/MyNavLink/index'
    import Home from './pages/Home/index'
    import About from './pages/About/index'
    
    export default class App extends Component {
        render() {
            return (
                

    React Router Demo

    {/* 编写路由链接 */} Home About
    {/* 注册路由 */}
    ) } }

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

    1. 默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
    2. 开启严格匹配:
    3. 严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由

    7)、Redirect的使用

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

    
    	
    	
    	
    
    

    8)、嵌套路由

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

    9)、向路由组件传递参数

    1)params参数

    路由链接(携带参数):

    {msgObj.title}
    

    注册路由(声明接收):

    
    

    接收参数:this.props.match.params

    父组件

    import React, { Component } from 'react'
    import { Link, Route } from 'react-router-dom'
    import Detail from './Detail/index'
    
    export default class Message extends Component {
        state = {
            messageArr: [
                { id: '01', title: '消息1' },
                { id: '02', title: '消息2' },
                { id: '03', title: '消息3' }
            ]
        }
        render() {
            const { messageArr } = this.state
            return (
                
      { messageArr.map((msgObj) => { return (
    • {/* 向路由组件传递params参数 */} {msgObj.title}  
    • ) }) }

    {/* 声明接收params参数 */}
    ) } }

    子组件

    import React, { Component } from 'react'
    
    const detailData = [
        { id: '01', content: '消息1详情' },
        { id: '02', content: '消息2详情' },
        { id: '03', content: '消息3详情' }
    ]
    export default class Detail extends Component {
        render() {
            //接收params参数
            const { id, title } = this.props.match.params
            const findResult = detailData.find((detailObj) => {
                return detailObj.id === id
            })
    
            return (
                
    • ID:{id}
    • TITLE:{title}
    • CONTENT:{findResult.content}
    ) } }
    2)search参数

    路由链接(携带参数):

    {msgObj.title}
    

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

    
    

    接收参数:this.props.location.search

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

    父组件

    import React, { Component } from 'react'
    import { Link, Route } from 'react-router-dom'
    import Detail from './Detail/index'
    
    export default class Message extends Component {
        state = {
            messageArr: [
                { id: '01', title: '消息1' },
                { id: '02', title: '消息2' },
                { id: '03', title: '消息3' }
            ]
        }
        render() {
            const { messageArr } = this.state
            return (
                
      { messageArr.map((msgObj) => { return (
    • {/* 向路由组件传递search参数 */} {msgObj.title}  
    • ) }) }

    {/* search参数无需声明接收,正常注册路由即可 */}
    ) } }

    子组件

    import React, { Component } from 'react'
    import qs from 'querystring'
    
    const detailData = [
        { id: '01', content: '消息1详情' },
        { id: '02', content: '消息2详情' },
        { id: '03', content: '消息3详情' }
    ]
    export default class Detail extends Component {
        render() {
            //接收search参数
            const { search } = this.props.location
            const { id, title } = qs.parse(search.slice(1))
            const findResult = detailData.find((detailObj) => {
                return detailObj.id === id
            })
    
            return (
                
    • ID:{id}
    • TITLE:{title}
    • CONTENT:{findResult.content}
    ) } }
    3)state参数

    路由链接(携带参数):

    {msgObj.title}
    

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

    
    

    接收参数:this.props.location.state

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

    父组件

    import React, { Component } from 'react'
    import { Link, Route } from 'react-router-dom'
    import Detail from './Detail/index'
    
    export default class Message extends Component {
        state = {
            messageArr: [
                { id: '01', title: '消息1' },
                { id: '02', title: '消息2' },
                { id: '03', title: '消息3' }
            ]
        }
        render() {
            const { messageArr } = this.state
            return (
                
      { messageArr.map((msgObj) => { return (
    • {/* 向路由组件传递state参数 */} {msgObj.title}  
    • ) }) }

    {/* state参数无需声明接收,正常注册路由即可 */}
    ) } }

    子组件

    import React, { Component } from 'react'
    
    const detailData = [
        { id: '01', content: '消息1详情' },
        { id: '02', content: '消息2详情' },
        { id: '03', content: '消息3详情' }
    ]
    export default class Detail extends Component {
        render() {
            //接收state参数
            const { id, title } = this.props.location.state || {}
            const findResult = detailData.find((detailObj) => {
                return detailObj.id === id
            }) || {}
    
            return (
                
    • ID:{id}
    • TITLE:{title}
    • CONTENT:{findResult.content}
    ) } }

    10)、编程式路由导航

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

    • this.prosp.history.push()
    • this.prosp.history.replace()
    • this.prosp.history.goBack()
    • this.prosp.history.goForward()
    • this.prosp.history.go()

    11)、BrowserRouter与HashRouter的区别

    1. 底层原理不一样

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

      HashRouter使用的是URL的哈希值

    2. path表现形式不一样

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

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

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

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

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

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

    视频资料

    https://www.bilibili.com/video/BV1wy4y1D7JT

    源码地址

    https://github.com/hxt970311/react_demo

    你可能感兴趣的:(后端的前端笔记,React脚手架,React路由)