一篇文章学会React
创始人
2024-01-20 14:39:54

React基础

文章目录

    • React基础
        • React介绍
        • React特点
        • React基本使用
            • 安装
            • 使用
        • React脚手架(***)
        • 生成的项目的目录结构
            • src下的index.js入口文件介绍
            • 脚手架中使用React
    • JSX
        • 什么是JSX
            • 产生原因
            • 介绍
            • JSX概述
            • JSX语法注意事项
            • JSX语法原理
        • JSX学习
            • 声明式渲染
            • 条件渲染
            • 循环渲染
            • HTML转义
        • 组件
            • class组件(有状态的组件)
            • 函数组件(无状态的组件)
            • 有状态和无状态的区别
        • 组件样式
            • 行内样式
            • 外链样式
        • 事件处理
            • 绑定事件
            • 事件处理函数的四种写法
        • 事件对象
        • 事件传参
        • ref属性
        • 组件的状态(state)
            • 什么是状态
        • react-devtools调试工具
    • 受控组件和非受控组件
        • 受控组件
            • 多个表单元素的受控
        • 非受控组件
  • 组件通信
      • 父传子
        • props
            • props的特点
            • 通过props属性模拟vue中的匿名插槽
            • 通过父传子组件通信模拟vue中的具名插槽
            • props属性值的验证
            • props属性的默认值
      • 子传父
      • 兄弟组件通信【了解】
      • context跨组件之间通信
  • 生命周期
        • 1.什么是生命周期
        • 2.生命周期三个阶段
        • 3.常用的生命周期钩子函数
        • 4.不常用的生命周期钩子函数
        • 5.React中性能优化的方案
  • React路由
        • react-router-dom基本使用
            • 1.下载react-router-dom 5的版本
            • 2.在index.js中从react-router-dom中引入 BrowserRouter和HashRouter
            • 3将App根组件用Router组件包裹起来
            • 4分别创建Home和Course组件
            • 5 在App组件中从react-router-dom中引入Route模块
            • 6通过Route模块的path属性和component属性设置路径和组件的对应关系
            • 7从react-router-dom中引入Link模块添加路由跳转
        • NavLink
        • 路由重定向
        • Switch
        • 模糊匹配和精确匹配
            • 步骤
        • 编程式导航
        • 二级路由
            • 基本配置
        • 动态路由
            • params方式
            • query方式
            • state方式
        • 补充
        • withRouter
        • 路由懒加载
        • 路由守卫

React介绍

官网:https://zh-hans.reactjs.org/

用于构建用户界面(UI —HTML页面)的 JavaScript 库

MVC角度来看,React仅仅是视图层的解决方案,只负责视图的渲染

react+reactDom /react-router/redux 框架

React起源于Facebook内部项目, Instragram网站,2013年5月开源React

最流行的前端开发框架之一 框架对比https://www.npmtrends.com/

框架:提供了一整套完整的解决方案
库: 一系列函数的集合

React特点

  • 声明式

    只需要描述UI看起来是什么样式,就跟写HTML一样,只负责渲染UI

  • 基于组件

    组件是React最重要的内容

  • 学习一次,随处可用

    可以开发web应用,开发移动端,还可以开发VR应用

React基本使用

安装
npm i react react-dom
  • react 是核心,提供了创建元素,组件等功能
  • react-dom提供dom相关的功能
使用
  • 引入react和react-dom

      
    
  • 创建元素

        // 创建元素节点// console.log(React)/* 创建元素节点1:元素名称2:元素属性 传递的是个对象3:元素内容*/// let title = React.createElement('li', null, 'hello React')// console.log(title)let title = React.createElement('p',{ title: '我是段落', id: 'p1' },React.createElement('span', null, '我是span标记'))
    
  • 渲染到页面

    // 渲染到页面上ReactDOM.render(title, document.getElementById('app'))
    

React脚手架(***)

零配置,无需手动配置繁琐的工具即可使用,只关注业务

  • 全局安装create-react-app

    npm i create-react-app -g
    
  • 创建项目

    create-react-app myreact(项目名称)
    

如果不想全局安装,可以使用npx

npx create-react-app my-pro

npx目的:提升包内提供的命令行工具的使用体验

之前:先安装脚手架工具,再使用包中提供的命令搭建项目

使用npx ,无需安装脚手架工具,就可以直接使用这个包提供的命令

create-react-app 是脚手架名称 不能随意更改

my-pro 是项目名称

注意

npx create-react-app my-pro这个过程会安装四个东西

  • cra-template 为create-react-app提供默认的模板
  • react: react的顶级库
  • react-dom 因为react有很多的运行环境,比如app端的react-native,要在web上运行就使用react-dom
  • react-scripts 包含运行和打包react应用程序的所有脚本以及配置

启动项目

npm start

生成的项目的目录结构

README.md  使用方法的文档
node_modules  所有依赖安装的目录
package-lock.json  锁定安装时的包的版本号,保证团队的依赖能保持一致
public  公共静态资源目录
src  开发用的源代码目录
src下的index.js入口文件介绍
// 从react包中引入React,引入该包就可以使用react的jsx语法
import React from 'react'
// 把自己写的react组件渲染到页面上,讲=将虚拟dom转换成真实的DOM
import ReactDOM from 'react-dom/client'
// 公共样式
import './index.css'
// 根组件
import App from './App'
// pwa渐进式开发相关的包,和react本身没有什么关系,可以不引入
import reportWebVitals from './reportWebVitals'// 指定了将我们的react组件渲染到哪里
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(// 这种在js中直接写html标签的语法叫做jsx语法,是react特有的属性
)// 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
// pwa相关的
reportWebVitals()
脚手架中使用React

index.js 入口文件

// 从react包中引入React,引入该包就可以使用react的jsx语法
import React from 'react'
// 把自己写的react组件渲染到页面上,讲=将虚拟dom转换成真实的DOM
import ReactDOM from 'react-dom/client'
// 公共样式
import './index.css'
// 根组件
import App from './App'// 指定了将我们的react组件渲染到哪里
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(// 这种在js中直接写html标签的语法叫做jsx语法,是react特有的属性
)

注意: 代表的是使用严格模式渲染组件,在严格模式下可以帮助我们

  • 识别不安全的生命周期
  • 关于使用过的字符串ref API的警告
  • 检测额外的副作用
  • 检测过时的context API

JSX

什么是JSX

产生原因

createElement创建元素,代码繁琐,结构不直观

介绍
React 为方便 View 层组件化,承载了构建 HTML 结构化页面的职责,即提供了JSX语法糖。JSX 将 
XHTML 语法直接加入到 JavaScript 代码中,再通过翻译器转换到纯 JavaScript 后由浏览器执行。 
在实际开发中,JSX 在产品打包阶段都已经编译成纯 JavaScript,不会带来任何副作用,反而会让 
代码更加直观并易于维护。
JSX概述
JSX是JavaScript XML的简写,表示在javaScript代码中可以直接写HTML格式的代码
好处:声明式语法更加直观,与HTML结构相同,学习成本低,效率高
JSX语法注意事项
  • jsx语法要求最外层只能有一个根节点

    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(
    

    欢迎学习React!!!

    React是专注于视图层的JavaScript库

    )
  • 如果真要想将两个并列的标签放在一个根节点中还不想产生额外的标签结构,可以使用

    React.Fragment 标签来包裹元素

    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(
    
    

    欢迎学习React!!!

    React是专注于视图层的JavaScript库

    React是目前最火的前端开发框架之一
    )
  • jsx语法实际上是js+xhtml的组合,因此要求单标签必须要闭合

  • 在jsx中要求 标签必须要有 alt 属性,否则会有警告

  • 在jsx语法中为了防止和js相关关键字冲突,要求 class 必须要写成 className ,label标签中的 for 属性必须要写成 htmlFor

    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(
    

    欢迎学习React!!!

    React是专注于视图层的JavaScript库

    )
JSX语法原理
  • JSX语法的原理实际上就是将我们的html结构用纯JavaScript对象来表示,看下面代码如何用纯

    JavaScript对象来描述

    欢迎学习React!!!

    React是专注于视图层的JavaScript库

  • 将以上html代码转成JavaScript对象就是这个样子:

    {"type": "div","props": {"id": "app","className": "app-root"},"children": [{"type": "h1","props": {"className": "title"},"children": ["欢迎学习React!!!"]},{"type": "p","props": null,"children": ["React是专注于视图层的JavaScript库"]}]
    }
    

    但是如果我们这么表示一个html结构的话写起来太不方便了,而且结构看起来也不清晰

  • React.js中扩展了JavaScript语法的功能,让html标签可以直接写在JavaScript中,然后React会自

    动帮我们将html标签转成纯JS对象。这样使用者写起来就方便多了,而且结构也更加的清晰了

    • 用户可以直接这样写

      const root = ReactDOM.createRoot(document.getElementById('root'));
      root.render(

      欢迎学习React!!!

      React是专注于视图层的JavaScript库

      )
  • React.js会使用@babel/preset-react 插件将JSX语法编译为 createElement() 方法

    root.render(
    React.createElement(
    "div",{"id": "app","className": "app-root"},
    React.createElement("h1",{"className": "title"},"欢迎学习React!!!"
    ),
    React.createElement("p",null,"React是专注于视图层的JavaScript库")
    )
    );
    
  • 然后React会将createElement创建的对象转化成React元素(纯js对象)

    {"type": "div","props": {"id": "app","className": "app-root"},"children": [{"type": "h1","props": {"className": "title"},"children": ["欢迎学习React!!!"]},{"type": "p","props": null,"children": ["React是专注于视图层的JavaScript库"]}]
    }
    

    总结:

    JSX语法的原理其实就是将我们写的 html标签 通过 @babel/preset-react 插件编译成

    createElement 方法,然后在转成 React元素 (纯js对象)

JSX学习

声明式渲染
  • 在React中用{} 来渲染属性或者变量, 这个{} 就相当于js的一个执行环境

    let msg = 'hello 大家好'
    let title = 'react专注于视图'
    root.render(
    {/*将要渲染的属性或者变量放在单花括号中 */}

    {msg}

    {/* 属性渲染 要渲染的属性写在 {}内就可以了 */}

    React专注于视图层

    )
条件渲染
  • React中的条件渲染主要是通过三目运算符或者短路运算符来完成,单括号中可以写表达式

    let flag = false
    root.render(
    {/* 使用三目运算符 */}{flag ? (
    我只能满足flag为true的条件时才能被渲染
    ) : (
    我只能满足flag为false的条件时才能被渲染
    )}
    {/* 使用短路 */}{false &&
    通过短路运算符来进行渲染
    }
    )
循环渲染
  • React中循环渲染主要是通过数组的map方法来完成的

    let arr = [{ id: 1, uname: 'aaaa' },{ id: 2, uname: 'bbbb' },{ id: 3, uname: 'ccccc' },
    ]root.render(
      {/* 数组的map方法 返回一个html标签组成的数组key 唯一标识*/}{arr.map((item) => (
    • {item.uname}
    • ))}
    )
HTML转义
  • 出于安全考虑,React中所有表达式的内容会被转义,如果直接输入,标签会被当做文本,为了防止xss攻击,如果要真的转成HTML标签 我们需要借助于dangerouslySetInnerHTML

    const ele = '
    我是strong标签
    ' root.render(
    {ele}
    )

    以上的写法html标签并没有被解析,如果我们希望被解析,我们这样做

    const ele = '
    我是strong标签
    ' root.render(
    { __html: ele }}>
    )

组件

class组件(有状态的组件)
  • React创建组件的方式使用类的继承, ES6 class是目前官方推荐的方式,使用了ES6标准语法来构建

    // 从React包中引入Component类,我们自己创建的所有组件都要继承Component类
    import { Component } from 'react'// 创建一个Child组件 继承Component
    class Child extends Component {render() {return 
    Child组件
    } }// 导出组件 export default Child

    注意点:

    我们创建的组件必须要继承React.Component这个父类

    必须要有render方法

    render方法中必须要返回React元素(JSX)

  • 在index.js中引入组件

    import React from 'react'
    import ReactDOM from 'react-dom/client'import Child from './Child'const root = ReactDOM.createRoot(document.getElementById('root'))
    root.render(
    )
    

    注意:组件名的首字母必须大写,为了和普通的html标签区分开

函数组件(无状态的组件)
  • 函数组件又叫做无状态的组件,定义一个函数组件就是定义一个函数,建议使用箭头函数

    import Child from './Child.js'
    // 创建一个函数组件
    const App = (props) => (

    欢迎大家学习React

    ) // 导出组件 export default App

    注意点:

    函数的首字母必须要大写,为了区分是一个组件不是一个普通函数

    函数中必须要返回react元素(JSX)

有状态和无状态的区别
  • 有状态组件:是一个class类,继承Component类,用于需要一些状态state去存储和修改数据,有生命周期
  • 无状态组件,是一个es6写的箭头函数,不继承Component类,用于一些简单的逻辑–渲染,无法使用state,没有生命周期

组件样式

行内样式
  • 给虚拟dom添加行内样式,使用表达式传入样式对象的方式来实现

  • React推荐我们使用行内样式,因为React觉得每一个组件都是一个独立的整体

     {/* 外面的{} 表示我们要在jsx里插入js 里面的是对象的 {} */}

    { color: '#c00', fontSize: '14px' }}>欢迎大家学习React

外链样式
  • 把css样式写在一个单独的css文件里,通过import导入

    //App.css
    .tit {background-color: yellow;
    }
    
  • App.js中导入

    import './App.css'const App = (props) => (

    123

    )

事件处理

绑定事件
  • React事件绑定采用的是 on+事件名的方法

  • 事件名的首字母要大写

  • React中的事件并不是原生的事件,而是合成事件,可以理解为React是对JavaScript的原生事件进行了封装

    class Child extends Component {render() {return (
    )} }
事件处理函数的四种写法
  • 直接在render里写行内的箭头函数,逻辑不复杂适用,事件处理函数中this指向当前对象

    class Child extends Component {msg = 'hello world'render() {return (
    {/* this指向当前对象 this.msg 打印出 hello world */}
    )} }
  • 在组件里定义一个非箭头函数,然后在render里直接使用,事件处理函数中this指向undefined,需要使用bind方法来改变this指向

    class Child extends Component {msg = 'hello world'render() {return (
    {/* 事件处理函数第二种 */}
    )}// 定义事件处理函数handleClick() {console.log(this.msg)} }

    没有bind改变this指向时 ,this为undefined的原因:

    这种写法相当于把handleClick这个类方法直接赋值给了button这个Dom元素的onClick属性,onClick这个方法是由window直接调用的,所以this本来应该是window,但是在React组件中的作用域是局部作用域并且开启了严格模式,严格模式下全局作用域函数中的this是undefined

  • 在组件内使用箭头函数定义一个方法,事件处理函数中this指向当前对象,但是不能事件传参

    class Child extends Component {msg = 'hello world'render() {return (
    {/* 事件处理函数第3种 */}
    )}// 定义事件处理函数handleClick = () => {console.log(this)} }
  • 直接在render函数里使用箭头函数定义一个事件处理函数,然后在事件处理函数中调用在组件内定义的方法(推荐)

    class Child extends Component {msg = 'hello world'render() {return (
    {/* 事件处理函数第3种 */} {this.handleClick()}}>点我-2
    )}// 定义事件处理函数handleClick = () => {console.log(this)} }

事件对象

和普通浏览器一样,事件处理函数会自动传入一个event对象,这个对象和普通的浏览器event对象所包含的属性和方法都基本一致,不同的是React中的event对象并不是浏览器提供的,而是它自己内部所构建的,它同样具有event.stopPropagation,event.preventDefault

import { Component } from 'react'class App extends Component {render() {return (
{this.handleClick(ev)}}>点我
)}// 定义事件处理函数handleClick(ev) {console.log(ev) //ev react内部构建的事件对象console.log(ev.nativeEvent) //获取的原生的事件对象} }export default App

事件传参

  • 事件传参的时候,事件处理函数的第一个参数是Event对象,其余参数依次向后排即可
import { Component } from 'react'class App extends Component {arr = ['aaa', 'bbb', 'ccc']render() {return (
{/* 多个参数 */}
    {this.arr.map((item, index) => ( {this.handleClicks(ev, index)}}>{item}))}
)}// 事件处理函数的第一个参数是Event对象,其余参数依次向后排即可handleClicks(ev, idx) {console.log(ev) //获取react内部所构建的事件对象console.log(idx)} }export default App

ref属性

ref就是给我们的标签或者组件起名字的,通过ref属性 我们可以获取到当前的标签或者组件

  • 给标签设置ref属性

    只有在非严格模式下才可以使用

    import { Component } from 'react'class App extends Component {arr = ['aaa', 'bbb', 'ccc']render() {return (
    {this.handleClick()}}>点我{/* 通过ref属性给标签起名字 */}

    欢迎学习React

    )}// 定义事件处理函数handleClick() {// 通过this.refs.名字来获取dom元素,这种写法只有在非严格模式下才可以使用 refs 弃用console.log(this.refs.uname)} }export default App
  • 新的写法

    import { Component, createRef } from 'react'import Child from './Child'class App extends Component {// 通过createRef方法创建一个ref实例对象uname = createRef()render() {return (
    {this.handleClick()}}>点我{/* 通过ref属性给标签起名字 */}{/*

    欢迎学习React

    */}
    )}// 定义事件处理函数handleClick() {// 通过this.uname.current方法来获取console.log(this.uname.current) //打印出p标签这个dom元素// 若是组件 获取到的是组件实例对象} }export default App

组件的状态(state)

什么是状态
  • 状态(state)就是React中挂载数据的地方,由React组件内部自己维护,它是私有的,通过state可以实现数据的响应式

  • 获取state的值通过 this.state.值的方法

  • 定义state

    • 第一种

      import { Component } from 'react'class App extends Component {// 定义state(简化写法)state = {msg: 'hello React',arr: ['aaa', 'bbb', 'ccc'],}render() {return (
      {/* 获取state中的值 */}
      {this.state.msg}
      {this.state.arr}
      )} }export default App
    • 第二种

      import { Component } from 'react'class App extends Component {// 构造器constructor(props) {super(props)// 定义statethis.state = {msg: 'hello React',arr: ['aaa', 'bbb'],}}render() {return (
      {/* 获取state中的值 */}
      {this.state.msg}
      {this.state.arr}
      )} }export default App
  • 修改state

    在React中通过this.state直接修改数据的话,react是无法得知的,所以需要使用特殊的修改状态的方法setState来修改state的状态

    import { Component } from 'react'class App extends Component {// 构造器constructor(props) {super(props)// 定义statethis.state = {msg: 'hello React',arr: ['aaa', 'bbb'],}}render() {return (
    {/* 获取state中的值 */}
    {this.state.msg}
    {this.state.arr}
    )}handleClick = () => {// this.state.msg = 'hello nihao' 错误的使用// 使用setState方法来修改statethis.setState({msg: '欢迎来学习React',arr: ['111'],})} }export default App
  • setState是异步的,这样可以将多次操作合并成一次操作节省性能

    import { Component } from 'react'class App extends Component {// 构造器constructor(props) {super(props)// 定义statethis.state = {msg: 'hello React',arr: ['aaa', 'bbb'],num: 0,}}render() {return (
    {/* 获取state中的值 */}
    {this.state.num}
    )}handleClick = () => {// 点击的时候多次调用setState方法this.setState({num: this.state.num + 1,})this.setState({num: this.state.num + 1,})this.setState({num: this.state.num + 1,})// 第一次点击网页显示结果为 0// 结果看出 React将三次setState操作合并成了一次操作,而且是先执行的console.log// 再执行setStateconsole.log(this.state.num)} }export default App
  • setState有两个参数

    • 第一个参数:对象或者是函数

      import { Component } from 'react'class App extends Component {// 构造器constructor(props) {super(props)// 定义statethis.state = {num: 0,}}render() {return (
      {/* 获取state中的值 */}
      {this.state.num}
      )}handleClick = () => {// 点击的时候多次调用setState方法this.setState((prevState, props) => {// prevState 上一次的state的值 --对象// props 组件传递的值return {num: prevState.num + 1,}})this.setState((prevState, props) => {return {num: prevState.num + 1,}})this.setState((prevState, props) => {return {num: prevState.num + 1,}})// setState第一个参数是函数的话,我们可以得到上一次state更新后的值} } export default App
    • 第二个参数:回调函数,用于得到setState修改完后的state的值,是可选的

      import { Component } from 'react'class App extends Component {// 构造器constructor(props) {super(props)// 定义statethis.state = {num: 0,msg: 'hello',}}render() {return (
      {/* 获取state中的值 */}
      {this.state.num}
      )}handleClick = () => {// 点击的时候多次调用setState方法this.setState((prevState, props) => {// prevState 上一次的state的值 --对象// props 组件传递的值return {num: prevState.num + 1,}},() => {//在回调函数内部 可以得到使用setState方法更新后的state的值,在回调函数外部是无法得到console.log(this.state.num, '回调函数') //1 最新的值})console.log(this.state.num) //0// setState第一个参数是函数的话,我们可以得到上一次state更新后的值} } export default App
  • 不要直接修改state中的数据后再通过setState进行赋值,正确的做法是将原有的数据复制一份

    import { Component } from 'react'class App extends Component {state = {arr: ['HTML', 'CSS', 'JavaScript', 'Vue'],}render() {return (
    {/* 获取state中的值 */}
      {this.state.arr.map((item, index) => (
    • {item}
    • ))}
    )}handleClick = () => {// 错误的写法// 直接修改state中的数据 再使用setState进行赋值,React中是不建议直接修改state中的数据的// this.state.arr.push('算术')// this.setState({// arr: this.state.arr,// })// 正确的写法// 先将state中要修改的数据复制一份const copyArr = [...this.state.arr]copyArr.push('算术') //操作复制出来的数组this.setState({arr: copyArr, //将复制出来的数组赋值给arr})} } export default App
  • setState响应式原理:一旦执行了setState方法,就会触发render方法,包括子组件的render方法

    import { Component } from 'react'// 子组件
    class Son extends Component {render() {console.log('子组件的render被执行了')return 
    我是子组件
    } }// 父组件 class App extends Component {state = {msg: 'hello',}render() {console.log('F组件的render被执行了')return (
    )}handleClick = () => {this.setState({msg: '123',})} } export default App

react-devtools调试工具

https://www.crx4chrome.com/crx/3068/

在这里插入图片描述

  • 将下载下来的后缀crx改为zip
  • 访问chrome谷歌浏览器的扩展程序页面打开网址chrome://extensions/
  • 打开开发者模式,把压缩包拖拽到扩展程序页面即可

受控组件和非受控组件

受控组件

  • 概念:用state来绑定表单元素的数据,是React的state成为表单元素的唯一数据源,这种既控制React组件渲染又控制着用户输入的过程的表单元素叫做受控组件

  • 受控组件的核心

    • 将state与表单元素的value值绑定在一起,复选框是checked属性
    • 在onChange中使用setState修改state中的数据
import { Component } from 'react'class App extends Component {state = {username: '123',desc: '',city: '3',sex: '男',checked: false,}render() {return (
{/* state的值和input的value属性绑定给input绑定onChange事件*/}
用户名:
描述:
城市:
性别: 男{this.state.sex}
)}changeUsername = (ev) => {// setState修改state的值this.setState({// 获取用户输入的值 赋值给usernameusername: ev.target.value,})}changeDesc = (ev) => {this.setState({desc: ev.target.value,})}changeCity = (ev) => {this.setState({city: ev.target.value,})}changeSex = (ev) => {this.setState({sex: ev.target.value,})}// 与input元素不同的是复选框绑定的是checked属性changeChecked = (ev) => {this.setState({checked: ev.target.checked,})} } export default App
多个表单元素的受控
  • 给表单元素添加name属性,名称和state相同
  • 根据表单元素的类型获取对应值
  • 在onChange事件处理程序中通过[name]修改对应的state
import { Component } from 'react'class App extends Component {state = {username: '123',desc: '',city: '3',sex: '男',checked: false,}render() {return (
{/* state的值和input的value属性绑定给input绑定onChange事件*/}
用户名:
描述:
城市:
性别: 男{this.state.sex}
)}handleChange = (ev) => {// 获取当前的表单元素const target = ev.target// 获取当前元素的name属性值const { name, value, type, checked } = target// 根据表单元素的type类型来判断是否为checkbox,来断定绑定的是checked还是valueconst val = type === 'checked' ? checked : valuethis.setState({[name]: val,})} } export default App

非受控组件

  • 借助于ref属性,通过操作原生DOM来获取表单数据的值,这样的表单元素称为非受控组件

  • React官方推荐我们使用受控组件来处理表单数据

    import { Component, createRef } from 'react'class App extends Component {// 1.创建一个ref对象txtRef = createRef()render() {return (
    用户名:{/* 2.将创建好的ref对象添加到输入框上 */}
    )}handleClick = (ev) => {// 3.通过ref对象获取到文本框的值console.log(this.txtRef.current.value)} } export default App

组件通信

父传子

思路:

  • 在父组件的子组件上绑定一个自定义属性,自定义属性的值就是我们要传递的数据
  • 在子组件中通过this.props来接收父组件传递过来的数据,函数组件是通过参数来接收

父组件

import { Component } from 'react'// 引入子组件
import Child from './Child'
import Son from './Son'
class App extends Component {state = {msg: '我是父组件的数据',}render() {return (

父组件

{/* 1.子组件上绑定自定义属性 */}
)} } export default App

子组件–类组件

import { Component } from 'react'class Child extends Component {render() {console.log(this.props)return (

子组件

{/* 2.在子组件中通过this.props来接收传递过来的数据 */}
{this.props.msg}
)} }export default Child

子组件–函数组件

function Son(props) {return 
{props.msg}
}export default Son
  • 在类组件中,如果使用了constructor构造函数,**应该将props传递给super,**否则无法在构造函数中获取到props

    class Child extends Component {// 如果使用了constructor构造函数,应该将props// 传递给super,否则无法在构造函数中获取到propsconstructor(props) {// 将props传递给supersuper(props)console.log(props, 'constructor')}render() {console.log(this.props)return (

    子组件

    {/* 2.在子组件中通过this.props来接收传递过来的数据 */}
    {this.props.msg}
    )} }

props

props的特点
  • 可以给组件传递任意类型的数据
  • props是只读的,只能读取属性的值,无法修改对象
通过props属性模拟vue中的匿名插槽
//父组件
import { Component } from 'react'// 引入子组件
import Child from './Child'
import Son from './Son'
class App extends Component {state = {msg: '我是父组件的数据',}render() {return (

父组件

{/* 1.子组件*/}

我是写在子组件标签元素内的元素

)} } export default App
//子组件
import { Component } from 'react'class Child extends Component {render() {console.log(this.props)return (

子组件

{/* 在子组件中通过this.props.children来接收写在子组件标签内的数据 */}
{this.props.children}
)} }export default Child
通过父传子组件通信模拟vue中的具名插槽
//父组件
import { Component } from 'react'// 引入子组件
import Child from './Child'
import Son from './Son'
class App extends Component {state = {msg: '我是父组件的数据',}render() {return (

父组件

{/* 1.在标签上绑定自定义属性,值为html标签*/}左边}right={}>
)} } export default App
//子组件
import { Component } from 'react'class Child extends Component {render() {console.log(this.props)return (

子组件

{this.props.left}我是子组件中的内容{this.props.right}
)} }export default Child
props属性值的验证
  • 有时候别人在使用我们封装好的组件的时候,传递的值和我们预期的值可能不一样,这样就会导致错误

    ,为了能够让用户快速的找到报错的原因及时修改,我们要对我们接收的props的值进行验证

    步骤:

    • 安装prop-types第三方包

      npm i prop-types
      or
      yarn add prop-types
      
    • 导入prop-typs包

    • 使用组件名.propTypes={}来给组件的props添加校验规则

    • 校验规则通过PropTypes对象来指定

    props属性值的约束规则

    常见类型 array,bool,func,number,object,string

    React元素的类型: element

    必填项:isRequired

    特定结构的对象:shape({})

//父组件
import { Component } from 'react'// 引入子组件
import Child from './Child'class App extends Component {state = {msg: '我是父组件的数据',arr: [11, 22, 33],obj: {name: 'zs',age: 12,},}render() {return (

父组件

)} } export default App
//子组件
import { Component } from 'react'
// 1.导入prop-typs第三方包
import propTypes from 'prop-types'class Child extends Component {render() {console.log(this.props)return (

子组件

{this.props.msg}
)} }// 2.通过组件名.propTypes添加校验规则 Child.propTypes = {// 约束msg的值为string类型msg: propTypes.bool,// 必填项arr: propTypes.array.isRequired,// 特定结构的对象// 要求person必须是一个对象,对象中必须包含name属性和age属性,值分别是string和number类型person: propTypes.shape({name: propTypes.string,age: propTypes.number,}), }/* props属性值的约束规则 常见类型 array,bool,func,number,object,string React元素的类型: element 必填项:isRequired 特定结构的对象:shape({}) */export default Child
props属性的默认值
  • 有时候为了让我们组件使用起来更方便,需要给组件设置默认值,在不传递该属性时生效,传递后以传递的值为准

  • 语法 组件名.defaultProps

    import { Component } from 'react'
    // 1.导入prop-typs第三方包
    import propTypes from 'prop-types'class Child extends Component {render() {console.log(this.props)return (

    子组件

    {this.props.msg}
    )} }// 2.通过组件名.propTypes添加校验规则 Child.propTypes = {// 约束msg的值为string类型msg: propTypes.string,// 必填项arr: propTypes.array.isRequired,// 特定结构的对象// 要求person必须是一个对象,对象中必须包含name属性和age属性,值分别是string和number类型person: propTypes.shape({name: propTypes.string,age: propTypes.number,}), } // 给msg属性设置默认值 Child.defaultProps = {msg: '我是msg的默认值', } export default Child

子传父

  • 思路:子传父组件利用的是回调函数的方法,在父组件中定义函数,在子组件中调用函数
  • 步骤:
    • 在父组件中定义一个回调函数
    • 在子组件的标签上定义一个自定义属性,值为定义的回调函数
    • 在子组件中通过this.props来调用这个函数,并将要传递的数据作为参数传递给该函数

父组件

import { Component } from 'react'// 引入子组件
import Child from './Child'class App extends Component {state = {msg: '我是父组件的数据',n: 0,}render() {return (

父组件

{this.state.n}
{/*2.在子组件的标签上绑定一个自定义属性,值为定义的回调函数 */}
)}// 1.在父组件中定义一个回调函数getChildData = (value) => {// value就是子组件传递过来的数据console.log(value)this.setState({n: value,})} } export default App

子组件

import { Component } from 'react'class Child extends Component {state = {num: 10,}render() {return (

子组件

)}handleClick = () => {// 3通过this.props来调用这个回调函数// 并将传递的数据(子传父的数据)作为参数传递给// 该函数this.props.getChildData(this.state.num)} }export default Child

兄弟组件通信【了解】

  • 思想:状态提升
  • 将共享的状态(数据)提升到最近的公共父组件,由父组件统一管理这个状态

父组件

import { Component } from 'react'// 引入子组件
import Child from './Child'
import Son from './Son'class App extends Component {// 1. 在父组件中定义共享数据state = {n: 0,}render() {return (

父组件

{this.state.n}
{/* 3.将回调函数传递给组件Child(组件A) */}
)}// 1.在父组件中定义一个回调函数addN = (value) => {this.setState({n: this.state.n + value,})} } export default App

组件A

import { Component } from 'react'class Child extends Component {state = {num: 10,}render() {return (

组件A

)}handleClick = () => {// 4.在组件A中通过this.props来调用函数this.props.add(this.state.num)} }export default Child

组件B

function Son(props) {return (
{/* 在组件B中通过props接收共享数据 */}
{props.num}
) }export default Son

context跨组件之间通信

  • Context可以实现将根组件中的数据传递给其任意的子组件数据,可以传递给儿子,也可以跨过儿子直接传递给孙子
  • 步骤:
    • 项目src目录下新建一个bus.js文件并调用createContext()方法,创建一个context对象,然后导出
    • 在App.js跟组件中从bus.js中导入provider组件,用来提供数据
    • 使用provider组件作为父节点
    • 设置vlaue属性,值为要传递的数据
    • 指定contextType,读取当前创建的context对象
    • 使用this.context来渲染值

bus.js

// 1.导入createContext方法
import { createContext } from 'react'// 2 创建Mcontext对象并设置默认值,也可以不设置,默认值在不提供provider组件时生效
const MyContext = createContext('我是默认值')// 3.创建Provider组件 (提供数据)  Consumer(消费数据)组件
const { Provider, Consumer } = MyContextexport default MyContextexport { Provider, Consumer }

根组件 App.js

import { Component } from 'react'// 引入子组件
import Child from './Child'// 1.引入Provider组件来提供数据
import { Provider } from './bus'
class App extends Component {render() {return (// 2.Provider组件作为根组件,并使用value属性提供数据

父组件

)} } export default App

Son.js

import { Component } from 'react'
// 引入 MyContext对象
import MyContext from './bus'
class Son extends Component {// 3.指定contextType读取当前创建的context对象// static 定义静态属性static contextType = MyContextrender() {return (

Son组件

{/* 通过this.context渲染 */}
{this.context}
)} }export default Son
  • 如果是函数组件通过Consumer来渲染数据
// 函数组件通过Consumer来渲染数据import { Consumer } from './bus'
function Son(props) {return (

Son组件

{/* Consumer组件的子节点是一个函数,返回html,函数的参数就是Provider组件提供的数据*/}{(data) => }
) } export default Son

生命周期

1.什么是生命周期

  • 生命周期就是组件从创建到销毁的过程
  • 生命周期钩子函数:生命周期的每个阶段总是伴随着一些方法调用,这些方法就叫生命周期钩子函数,生命周期的钩子函数为我们在不同阶段操作组件提供了时机
  • 只有类组件才有生命周期

2.生命周期三个阶段

  • React组件的生命周期分为三个阶段:创建阶段,更新阶段,卸载阶段
  • 每个阶段常用的钩子函数及触发时机

在这里插入图片描述

3.常用的生命周期钩子函数

  • 创建(挂载)阶段(组件创建时)

    执行顺序:constructor----->render------->componentDidMount

    • constructor

      时机:创建组件时最先执行;

      作用:1:初始化state2:为事件处理程序绑定this

    • render

      时机:每次组件渲染都会触发

      作用:渲染视图**(注意:不能调用setState)**

    • componentDidMount

      时机:组件挂载(完成DOM渲染后)

      作用:1:发送网络请求2:DOM操作

代码

import { Component } from 'react'class App extends Component {constructor() {super() //thisconsole.log('constructor')}render() {console.log('render')return (

AP片组件

)}componentDidMount() {console.log('componentDidMount')} } export default App
  • 更新阶段

    使用this.setState更新状态

    组件的props属性发生变化

    强制更新,调用forceupdate()

    执行顺序:render-------->componentDidUpdate

    • render

      时机:每次组件渲染都会触发

      作用:渲染视图**(注意:不能调用setState)**

    • componentDidUpdate

      时机:组件更新(完成DOM渲染后)

      作用:1:发送网络请求2:DOM操作

      ​ 注意:如果要setState()必须放在一个if操作中

      属性: componentDidUpdate(prveProps,prevState)

        		prevProps 更新之前的props值prevState更新之前的state值我们可以对比更新之前的props和state和更新之后的props和state是否一致,来决定是否调用				setState
      

代码

import { Component } from 'react'class Child extends Component {state = {count: 0,}render() {console.log('render', 'Child')return (

Child组件


{this.props.num}---{this.state.count}

)}componentDidUpdate(prevProps, prevState) {// prevProps 更新之前的props值// prevState更新之前的state值// 我们可以对比更新之前的props和state和更新之后的props和state是否一致,// 来决定是否调用setStateconsole.log('componentDidUpdate')console.log(prevProps, this.props)console.log(prevState, this.state)}plus = () => {this.setState({count: this.state.count + 5,})} }export default Child
  • 卸载阶段

    • componentWillUnMount

      时机:组件卸载(从页面中消失)

      作用:执行清理工作(清理定时器,事件)

父组件

import { Component } from 'react'import Child from './Child'
class App extends Component {constructor() {super() //thisthis.state = {num: 10,isshow: true,}}render() {return (

AP片组件

{this.state.isshow ? : null}
)}toggle = () => {this.setState({isshow: !this.state.isshow,})}add = () => {this.setState({num: this.state.num + 1,})} } export default App

子组件

import { Component } from 'react'class Child extends Component {state = {count: 0,}render() {console.log('render', 'Child')return (

Child组件


{this.props.num}---{this.state.count}

)}componentDidUpdate(prevProps, prevState) {// prevProps 更新之前的props值// prevState更新之前的state值// 我们可以对比更新之前的props和state和更新之后的props和state是否一致,// 来决定是否调用setStateconsole.log('componentDidUpdate')console.log(prevProps, this.props)console.log(prevState, this.state)}componentWillUnmount() {console.log('componentWillUnmount')}plus = () => {this.setState({count: this.state.count + 5,})} }export default Child

4.不常用的生命周期钩子函数

完整的生命周期钩子函数

在这里插入图片描述

  • static getDerivedStateFromProps

    • getDerivedStateFromProps属于静态方法,所以不能在该方法中访问组件实例

    • 执行时机:组件创建时,组件更新时

    • 执行顺序:该方法在render方法之前执行

    • 该方法必须返回一个对象来更新state,如果返回null则不更新任何内容

    • static getDerivedStateFromProps(props,state)

App.js

import { Component } from 'react'import List from './List'
import FooterCmp from './FooterCmp'class App extends Component {state = {arrList: [{ id: 1, checked: false, name: 'aaaa' },{ id: 2, checked: false, name: 'bbbb' },{ id: 3, checked: false, name: 'cccc' },{ id: 4, checked: false, name: 'dddd' },{ id: 5, checked: false, name: 'eeee' },],}render() {return (
)}handleChange = (id) => {// 单选// console.log(id)const copyArrList = [...this.state.arrList]const idx = copyArrList.findIndex((item) => item.id === id)copyArrList[idx].checked = !copyArrList[idx].checkedthis.setState({arrList: copyArrList,})}// 全选chooseAll = (checked) => {const copyArrList = [...this.state.arrList]copyArrList.forEach((item) => {item.checked = checked})this.setState({arrList: copyArrList,})} } export default App

List.js

import { Component } from 'react'class List extends Component {render() {return (
{this.props.arrList.map((item) => (
this.handleChange(item.id)}/>{item.name}
))}
)}handleChange = (id) => {this.props.handleList(id)} } export default List

FooterCmp.js

import { Component } from 'react'class FooterCmp extends Component {state = {checked: false,}static getDerivedStateFromProps(nextProps, nextState) {// nextProps 下一次的props属性值// nextState下一次的state属性值// 当父组件传递过来的arrList数组发生变化时返回最新的statereturn {checked: nextProps.arrList.every((item) => item.checked),}}render() {return (
全选:
)}changeChecked = (ev) => {this.props.chooseAll(ev.target.checked)// 更改state中checked的值} } export default FooterCmp
  • shoundComponentUpdate

    • 组件重新渲染前执行

    • 根据shoundComponentUpdate的返回值来决定是否更新自身组件以及子组件,返回true更新返回false不更新

    • 此方法作为性能优化的一种方案,不能企图依赖该方法来阻止渲染

    • 最好使用React提供的内置组件PureComponent来自动判断是否调用render方法,而不是使用shoundComponentUpdate方法来进行手动判断

    • 不建议在shoundComponentUpdate中进行深层比较或者使用JSON.stringify

    • shoundComponentUpdate(nextProps,nextState)

      nextProps更新完成后的props值

      nextState更新完成后的state值

代码

import { Component } from 'react'class App extends Component {state = {num: 0,}shouldComponentUpdate(nextProps, nextState) {// 判断上一次的state和更新后的state是否相等,来决定是否调用render来更新组件return nextState !== this.state.num}render() {console.log('render')return (

父组件

{this.state.num}
)}handleClick = () => {this.setState({num: parseInt(Math.random() * 3),})} } export default App

5.React中性能优化的方案

  • 减轻state
    • 在state中只存储和组件渲染有关的数据
    • 不做渲染的数据不放在state中,直接挂载在this上即可, 比如定时器Id
import { Component } from 'react'class App extends Component {componentDidMount() {//dom渲染完成// 开启定时器// timerid 存储到this中,而不是state中this.timerId = setInterval(() => {}, 1000)}componentWillUnmount() {// 卸载clearInterval(this.timerId)}render() {return 
} } export default App
  • 避免不必要的重新渲染

    • 使用shoundComponentUpdate钩子函数手动更新组件

    • 使用纯组件 PureComponent自动更新组件,使用方式和componet一致

      PureComponent会分别对前后两次props和state进行浅层对比

      浅层比较:

      对于基本数据类型直接比较两个值是否相同

      引用数据类型比较的是地址,所以不要直接修改state中的引用数据类型,应该创建一个新的实例

import { PureComponent } from 'react'class App extends PureComponent {state = {num: 0,}render() {console.log('render')return (

{ padding: '0 10px' }}>{this.state.num}
)}handleClick = () => {this.setState({num: parseInt(Math.random() * 3),})} } export default App

React路由

我们访问的路径和React组件之间的对应关系

react-router-dom基本使用

1.下载react-router-dom 5的版本
npm i react-router-dom@5
2.在index.js中从react-router-dom中引入 BrowserRouter和HashRouter

BrowserRouter使用历史模式history来管理路由

HashRouter使用哈希模式hash来管理路由

// 将引入的BrowserRouter模块取一个别名 叫做Router
import { BrowserRouter as Router } from 'react-router-dom'
3将App根组件用Router组件包裹起来

React-router-dom路由系统要求一个路由系统只能有一个Router

root.render({/* 将App组件用Router包裹起来 */}
)
4分别创建Home和Course组件
import { Component } from 'react'
export default class Course extends Component {render() {return 
Home
} }
import { Component } from 'react'
export default class Home extends Component {render() {return 
Home
} }
5 在App组件中从react-router-dom中引入Route模块
// 引入Route模块
import { Route } from 'react-router-dom'
6通过Route模块的path属性和component属性设置路径和组件的对应关系
import Course from './Course'
import Home from './Home'
class App extends PureComponent {render() {return (
)} }
7从react-router-dom中引入Link模块添加路由跳转
// 引入Route模块
import { Link, Route } from 'react-router-dom'import Course from './Course'
import Home from './Home'
class App extends PureComponent {render() {return (
{/* 路由跳转Link模块的to属性可以是一个字符串,也可以是一个对象*/}{/* 首页 */}{ pathname: '/home' }}>首页课程
)} }

NavLink

当我们希望给当前的路由添加一个样式的时候,我们可以使用NavLink模块来代替Link模块

给NavLink添加activeClassName属性,属性名为class名,在样式中定义active样式

App.js

import { PureComponent } from 'react'import './App.css'// 引入Route模块
import { Link, NavLink, Route } from 'react-router-dom'import Course from './Course'
import Home from './Home'
class App extends PureComponent {render() {return (
{/* 路由跳转*/}首页课程{/* 路由出口 */}
)} } export default App

App.css

a {text-decoration: none;margin: 0 10px;
}
.active {color: #c00;
}

路由重定向

我们希望一进入页面就可以访问到/home页面,使用Redirect模块

// 引入Route模块
import { Link, NavLink, Route, Redirect } from 'react-router-dom'import Course from './Course'
import Home from './Home'
export default class App extends PureComponent {render() {return (
{/* 路由跳转*/}首页课程{/* 路由出口 */}{/* 访问根路径的时候,重定向到/home */}{/* from 从那个路径来 to 要跳转到那个路径 */}
)} }

Switch

目前路由存在问题,切换到Course页面,当我们刷新页面的时候就回到了Home页面

解决上面问题使用Switch模块

import { Link, NavLink, Route, Redirect, Switch } from 'react-router-dom'import Course from './Course'
import Home from './Home'
export default class App extends PureComponent {render() {return (
{/* 路由跳转*/}首页课程{/* 将路由表用Switch组件包裹起来 */}{/* 路由出口 */}{/* 访问根路径的时候,重定向到/home */}{/* from 从那个路径来 to 要跳转到那个路径 */}
)} }

解释:

我们的路由表会从头到尾执行一遍,即使他已经匹配到当前路径,但是它也会把下面的路由执行一遍,

那么久导致了我们最后无论如何都会执行到Redirect,就被重新定向到Home页面了

Switch作用就是让当前路由如果匹配到,就不再向下继续匹配

模糊匹配和精确匹配

我们访问一个不存在的路径,我们希望把这个不存在的路径重定向到404页面

步骤
  • 定义一个NotFound页面

    import { Component } from 'react'
    export default class NotFound extends Component {render() {return 
    NotFound
    } }
  • 通过Route定义访问任何路径,都去访问NotFound页面

     {/* 将路由表用Switch组件包裹起来 */}{/* 路由出口 */}{/* 访问根路径的时候,重定向到/home */}{/* from 从那个路径来  to 要跳转到那个路径 */}
    

    问题:我们并没有机会访问到NotFound页面,每次访问不存在的路径都会重定向到Home页面

    因为我们的根路径/会匹配到任何路径,任何路径都是以 / 开头的,我们把 /这样的路径叫做父路径

    /home /course叫做 / 的子路径。父路径会匹配它下面所有的子路径,是模糊匹配

    比如 /home会匹配到 /home/aaa ,/home/bbb;因为 /home/aaa ,/home/bbb是/home的子路径

    所以在Redirect中需要精确匹配到/ ,而 / 的子路径不能被匹配到,我们需要使用exact属性

 {/* 将路由表用Switch组件包裹起来 */}{/* 路由出口 */}{/* 访问根路径的时候,重定向到/home */}{/* from 从那个路径来  to 要跳转到那个路径 */}

编程式导航

  • 声明式导航

    通过NavLink来实现路由跳转的导航

  • 编程式导航

    通过js来实现路由的跳转

    export default class NotFound extends Component {render() {return (
    )}handleClick = () => {console.log(this.props)this.props.history.push('/course')} }

    history对象的常用方法

    • push路由跳转,push里面传递的参数和NavLink中的to属性的参数是一样的
    • go(num) 前进或后退num级
    • goBack() go(-1) 后退一级
    • goForward() go(1) 前进一级

二级路由

在这里插入图片描述

基本配置
//App.js
export default class App extends PureComponent {render() {return (
{/* 路由跳转*/}首页新闻课程
)} }

课程下配置二级路由

import { Component } from 'react'
import { NavLink, Redirect, Route, Switch } from 'react-router-dom'
import JsCourse from './course/JsCourse'
import VueCourse from './course/VueCourse'
import ReactCourse from './course/ReactCourse'
export default class Course extends Component {render() {return (

Course


js课程vue课程react课程
)} }

动态路由

动态路由传参有三种方式:

params方式
  • 注册路由的时候通过:params的形式来声明

    {/* 通过 :id来声明我们要传递的参数是id */}
    
    
  • 路由匹配

     {this.state.arr.map((item) => (
  • {item.tit}
  • ))}
        
      {this.state.arr.map((item) => ( {this.props.history.push('/detail/' + item.id)}}key={item.id}>{item.tit}))}
  • 在组件中获取params方式传递参数

     const { id } = this.props.match.params
    
query方式
  • query方式传递的参数不需要在注册路由的时候声明

    
    
  • 路由匹配

      {this.state.arr.map((item) => (
    • {item.tit}
    • ))}
  • 在组件中获取query方式传递的参数

    this.props.location.search.split('=')[1]
    
state方式
  • state方式传递的参数也不需要在注册路由的时候声明

    
    
  • 路由匹配

      {this.state.arr.map((item) => (
    • { pathname: '/dstate', state: { id: 5 } }}>{item.tit}
    • ))}
  • 在组件中获取传递的参数

    this.props.location.state
    

补充

只有被路由管理的组件的props属性上才具有路由的三大对象:history,location,match

  • 当前组件必须是通过Route组件渲染出来的,该组件的props上才有路由的三大对象

HomeChild.js

import { Component } from 'react'export default class HomeChild extends Component {componentDidMount() {// 因为HomeChild这个组件是直接渲染在Home组件中,并不是通过Route组件渲染处理// 所以HomeChild这个组件并没有被路由所管理,该组件的props不具有路由的三大属性console.log(this.props, 'homechild') // {}}render() {return (

我是home组件的子组件

)} }

Home.js

import { Component } from 'react'import HomeChild from './HomeChild'
export default class Home extends Component {render() {return (
home

)} }
  • 如果想要子组件也具有路由的三大对象,我们可以通过传参的方式,将父组件上的路由三大对象传递给子组件
import { Component } from 'react'import HomeChild from './HomeChild'
export default class Home extends Component {render() {return (
home
{/* 将父组件的props传递给子组件 */}
)} }

withRouter

  • withRouter模块可以让没有被路由管理的组件也可以获取到路由的三大对象

    //HomeChild.js
    import { Component } from 'react'
    import { withRouter } from 'react-router-dom'class HomeChild extends Component {componentDidMount() {// 因为HomeChild这个组件是直接渲染在Home组件中,并不是通过Route组件渲染处理// 所以HomeChild这个组件并没有被路由所管理,该组件的props不具有路由的三大属性console.log(this.props, 'homechild') // {}}render() {return (

    我是home组件的子组件

    )} }// withRouter方法接收一个组件作为参数,返回一个具有路由三大对象的组件 export default withRouter(HomeChild)

路由懒加载

React中的路由懒加载是基于React.lazy方法和Suspense组件来实现的

import React, { PureComponent, Suspense } from 'react'import './App.css'// 引入Route模块
import { Link, NavLink, Route, Redirect, Switch } from 'react-router-dom'const Course = React.lazy(() => import('./Course'))
const Home = React.lazy(() => import('./Home'))
const News = React.lazy(() => import('./News'))
const Detail = React.lazy(() => import('./Detail'))
const Details = React.lazy(() => import('./Details'))
const Dstate = React.lazy(() => import('./Dstate'))
const NotFound = React.lazy(() => import('./NotFound'))export default class App extends PureComponent {render() {return (
{/* 路由跳转*/}首页新闻课程加载中...
}>{/* 将路由表用Switch组件包裹起来 */}{/* 路由出口 */}{/* 通过 :id来声明我们要传递的参数是id */}{/* 访问根路径的时候,重定向到/home */}{/* from 从那个路径来 to 要跳转到那个路径 */}
)} }

路由守卫

登录拦截案例

  • 请求路径:http://shiyansong.cn:8888/api/private/v1/
  • 请求方法:post
  • 请求参数 username:admin password:123456

Login.js

import React, { Component } from 'react'
import axios from 'axios'export default class Login extends Component {state = {username: '',password: '',}render() {return (
{this.handleChange(ev)}}/>
s {this.handleChange(ev)}}name="password"/>
)}// 受控组件handleChange = (ev) => {const el = ev.targetthis.setState({[el.name]: el.value,})}login = async () => {// 发送登录请求const {data: { data: res, meta },} = await axios.post('http://shiyansong.cn:8888/api/private/v1/login',this.state)// console.log(res)// 根据返回的状态码判断是否登录成功if (meta.status === 200) {// 登录成功将token保存到本地localStorage.setItem('token', res.token)// 跳转到首页this.props.history.push('/home')}} }

Home.js

import React, { Component } from 'react'export default class Home extends Component {render() {return (

首页面

)}logout = () => {localStorage.removeItem('token')this.props.history.push('/login')} }

App.js

import React, { PureComponent, Suspense } from 'react'import './App.css'// 引入Route模块
import { Route, Redirect, Switch } from 'react-router-dom'const Login = React.lazy(() => import('./Login'))
const Home = React.lazy(() => import('./Home'))
const NotFound = React.lazy(() => import('./NotFound'))export default class App extends PureComponent {// 验证是否登录成功的函数loginVerfiy = () => {if (localStorage.getItem('token')) {return true} else {return false}}render() {return (
加载中...
}>{/* render属性来渲染组件 */}this.loginVerfiy() ? : }>
)} }

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
阳澄湖在哪里哪个省的 阳澄湖是... 点击题目下方苏州生活指南有用 有趣 有态度近期的阳澄湖度很高,3月24日,央视二套《第一时间》直播连...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
金属硬度排行 各种金属硬度 世界上最硬的东西排名分别为硫化碳块,石墨烯,金刚石,金属玻璃和金属锇。这五种物体几乎是整个世界上最坚...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...