一篇文章学会React

React基础

文章目录

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

      <script src="./node_modules/react/umd/react.development.js"></script>
      <script src="./node_modules/react-dom/umd/react-dom.development.js"></script>
    
  • 创建元素

        // 创建元素节点
        // 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特有的属性
  <React.StrictMode>
    <App />
  </React.StrictMode>
)

// 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特有的属性
  <React.StrictMode>
    <App />
  </React.StrictMode>
)

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

  • 识别不安全的生命周期
  • 关于使用过的字符串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(
    <div>
        <h1>欢迎学习React!!!</h1>
        <p>React是专注于视图层的JavaScript库</p>
    </div>
    )
    
  • 如果真要想将两个并列的标签放在一个根节点中还不想产生额外的标签结构,可以使用

    React.Fragment 标签来包裹元素

    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(
    <React.Fragment>
    <div>
        <h1>欢迎学习React!!!</h1>
        <p>React是专注于视图层的JavaScript库</p>
        </div>
        <div>React是目前最火的前端开发框架之一</div>
    </React.Fragment>
    )
    
  • jsx语法实际上是js+xhtml的组合,因此要求单标签必须要闭合

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

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

    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(
    <React.Fragment>
        <div className="app-root">
        <h1 className="title">欢迎学习React!!!</h1>
        <p>React是专注于视图层的JavaScript库</p>
        </div>
        <form>
        <label htmlFor="username">用户名:</label>
        <input id="username" name="username" placeholder="请输入用户名" />
        </form>
    </React.Fragment>
    )
    
JSX语法原理
  • JSX语法的原理实际上就是将我们的html结构用纯JavaScript对象来表示,看下面代码如何用纯

    JavaScript对象来描述

    <div id="app" className="app-root">
        <h1 className="title">欢迎学习React!!!</h1>
        <p>React是专注于视图层的JavaScript库</p>
    </div>
    
  • 将以上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(
          <div id="app" className="app-root">
              <h1 className="title">欢迎学习React!!!</h1>
              <p>React是专注于视图层的JavaScript库</p>
      	</div>
      )
      
  • 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(
      <div id="app" className="app-root">
        {/*将要渲染的属性或者变量放在单花括号中 */}
        <h1>{msg}</h1>
        {/* 属性渲染 要渲染的属性写在 {}内就可以了 */}
        <p title={title}>React专注于视图层</p>
      </div>
    )
    
条件渲染
  • React中的条件渲染主要是通过三目运算符或者短路运算符来完成,单括号中可以写表达式

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

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

    const ele = '<div><strong>我是strong标签</strong></div>'
    root.render(
      <React.StrictMode>
        <div className="app-root">{ele}</div>
      </React.StrictMode>
    )
    

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

    const ele = '<div><strong>我是strong标签</strong></div>'
    root.render(
      <React.StrictMode>
        <div className="app-root" dangerouslySetInnerHTML={{ __html: ele }}></div>
      </React.StrictMode>
    )
    
组件
class组件(有状态的组件)
  • React创建组件的方式使用类的继承, ES6 class是目前官方推荐的方式,使用了ES6标准语法来构建

    // 从React包中引入Component类,我们自己创建的所有组件都要继承Component类
    import { Component } from 'react'
    
    // 创建一个Child组件 继承Component
    class Child extends Component {
      render() {
        return <div>Child组件</div>
      }
    }
    
    // 导出组件
    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(
      <React.StrictMode>
        <Child />
      </React.StrictMode>
    )
    

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

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

    import Child from './Child.js'
    // 创建一个函数组件
    const App = (props) => (
      <div className="app-root">
        <h1>欢迎大家学习React</h1>
        <Child />
      </div>
    )
    // 导出组件
    export default App
    

    注意点:

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

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

有状态和无状态的区别
  • 有状态组件:是一个class类,继承Component类,用于需要一些状态state去存储和修改数据,有生命周期
  • 无状态组件,是一个es6写的箭头函数,不继承Component类,用于一些简单的逻辑–渲染,无法使用state,没有生命周期
组件样式
行内样式
  • 给虚拟dom添加行内样式,使用表达式传入样式对象的方式来实现

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

     {/* 外面的{} 表示我们要在jsx里插入js 里面的是对象的 {} */}
        <h1 style={{ color: '#c00', fontSize: '14px' }}>欢迎大家学习React</h1>
    
外链样式
  • 把css样式写在一个单独的css文件里,通过import导入

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

    import './App.css'
    
    const App = (props) => (
      <div className="app-root">  
        <p className="tit">123</p>
      </div>
    )
    
事件处理
绑定事件
  • React事件绑定采用的是 on+事件名的方法

  • 事件名的首字母要大写

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

    class Child extends Component {
      render() {
        return (
          <div>
            <button onClick={() => console.log('hello')}>点我</button>
          </div>
        )
      }
    }
    
事件处理函数的四种写法
  • 直接在render里写行内的箭头函数,逻辑不复杂适用,事件处理函数中this指向当前对象

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

    class Child extends Component {
      msg = 'hello world'
      render() {
        return (
          <div>
            {/* 事件处理函数第二种 */}
            <button onClick={this.handleClick.bind(this)}>点我-2</button>
          </div>
        )
      }
      // 定义事件处理函数
      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 (
          <div>
            {/* 事件处理函数第3种 */}
            <button onClick={this.handleClick}>点我-2</button>
          </div>
        )
      }
      // 定义事件处理函数
      handleClick = () => {
        console.log(this)
      }
    }
    
  • 直接在render函数里使用箭头函数定义一个事件处理函数,然后在事件处理函数中调用在组件内定义的方法(推荐)

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

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

import { Component } from 'react'

class App extends Component {
  render() {
    return (
      <div id="app">
        <button
          onClick={(ev) => {
            this.handleClick(ev)
          }}
        >
          点我
        </button>
      </div>
    )
  }
  // 定义事件处理函数
  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 (
      <div id="app">
        {/* 多个参数 */}
        <ul>
          {this.arr.map((item, index) => (
            <li
              key={index}
              onClick={(ev) => {
                this.handleClicks(ev, index)
              }}
            >
              {item}
            </li>
          ))}
        </ul>
      </div>
    )
  }
  // 事件处理函数的第一个参数是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 (
          <div id="app">
            <button
              onClick={() => {
                this.handleClick()
              }}
            >
              点我
            </button>
            {/* 通过ref属性给标签起名字 */}
            <p ref="uname">欢迎学习React</p>
          </div>
        )
      }
      // 定义事件处理函数
      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 (
          <div id="app">
            <button
              onClick={() => {
                this.handleClick()
              }}
            >
              点我
            </button>
            {/* 通过ref属性给标签起名字 */}
            {/* <p ref={this.uname}>欢迎学习React</p> */}
    
            <Child ref={this.uname}></Child>
          </div>
        )
      }
      // 定义事件处理函数
      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 (
            <div id="app">
              {/* 获取state中的值 */}
              <div>{this.state.msg}</div>
              <div>{this.state.arr}</div>
            </div>
          )
        }
      }
      
      export default App
      
    • 第二种

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

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

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

    import { Component } from 'react'
    
    class App extends Component {
      // 构造器
      constructor(props) {
        super(props)
        // 定义state
        this.state = {
          msg: 'hello React',
          arr: ['aaa', 'bbb'],
          num: 0,
        }
      }
      render() {
        return (
          <div id="app">
            {/* 获取state中的值 */}
    
            <button onClick={this.handleClick}>修改</button>
            <div>{this.state.num}</div>
          </div>
        )
      }
      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
        // 再执行setState
        console.log(this.state.num)
      }
    }
    
    export default App
    
    
  • setState有两个参数

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

      import { Component } from 'react'
      
      class App extends Component {
        // 构造器
        constructor(props) {
          super(props)
          // 定义state
          this.state = {
            num: 0,
          }
        }
        render() {
          return (
            <div id="app">
              {/* 获取state中的值 */}
      
              <button onClick={this.handleClick}>修改</button>
              <div>{this.state.num}</div>
            </div>
          )
        }
        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)
          // 定义state
          this.state = {
            num: 0,
            msg: 'hello',
          }
        }
        render() {
          return (
            <div id="app">
              {/* 获取state中的值 */}
      
              <button onClick={this.handleClick}>修改</button>
              <div>{this.state.num}</div>
            </div>
          )
        }
        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 (
          <div id="app">
            {/* 获取state中的值 */}
    
            <button onClick={this.handleClick}>修改</button>
            <ul>
              {this.state.arr.map((item, index) => (
                <li key={index}>{item}</li>
              ))}
            </ul>
          </div>
        )
      }
      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 <div>我是子组件</div>
      }
    }
    
    // 父组件
    class App extends Component {
      state = {
        msg: 'hello',
      }
      render() {
        console.log('F组件的render被执行了')
        return (
          <div id="app">
            <button onClick={this.handleClick}>点我</button>
            <Son></Son>
          </div>
        )
      }
      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 (
      <div id="app">
        {/* state的值和input的value属性绑定
            给input绑定onChange事件
        */}
        <div>
          用户名:
          <input
            type="text"
            value={this.state.username}
            onChange={this.changeUsername}
          />
        </div>
        <div>
          描述:
          <textarea
            value={this.state.desc}
            onChange={this.changeDesc}
          ></textarea>
        </div>
        <div>
          城市:
          <select value={this.state.city} onChange={this.changeCity}>
            <option value="0">北京</option>
            <option value="1">上海</option>
            <option value="2">广州</option>
            <option value="3">西安</option>
          </select>
        </div>
        <div>
          性别: 男
          <input name="sex" type="radio" value="男" onChange={this.changeSex} />
          女
          <input name="sex" type="radio" value="女" onChange={this.changeSex} />
          {this.state.sex}
        </div>

        <div>
          <input
            type="checkbox"
            checked={this.state.checked}
            onChange={this.changeChecked}
          />
        </div>
      </div>
    )
  }
  changeUsername = (ev) => {
    // setState修改state的值
    this.setState({
      // 获取用户输入的值 赋值给username
      username: 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 (
      <div id="app">
        {/* state的值和input的value属性绑定
            给input绑定onChange事件
        */}
        <div>
          用户名:
          <input
            name="username"
            type="text"
            value={this.state.username}
            onChange={this.handleChange}
          />
        </div>
        <div>
          描述:
          <textarea
            name="desc"
            value={this.state.desc}
            onChange={this.handleChange}
          ></textarea>
        </div>
        <div>
          城市:
          <select
            name="city"
            value={this.state.city}
            onChange={this.handleChange}
          >
            <option value="0">北京</option>
            <option value="1">上海</option>
            <option value="2">广州</option>
            <option value="3">西安</option>
          </select>
        </div>
        <div>
          性别: 男
          <input
            name="sex"
            type="radio"
            value="男"
            onChange={this.handleChange}
          />
          女
          <input
            name="sex"
            type="radio"
            value="女"
            onChange={this.handleChange}
          />
          {this.state.sex}
        </div>

        <div>
          <input
            type="checkbox"
            name="checked"
            checked={this.state.checked}
            onChange={this.handleChange}
          />
        </div>
      </div>
    )
  }
  handleChange = (ev) => {
    // 获取当前的表单元素
    const target = ev.target
    // 获取当前元素的name属性值
    const { name, value, type, checked } = target

    // 根据表单元素的type类型来判断是否为checkbox,来断定绑定的是checked还是value

    const val = type === 'checked' ? checked : value

    this.setState({
      [name]: val,
    })
  }
}
export default App

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

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

    import { Component, createRef } from 'react'
    
    class App extends Component {
      // 1.创建一个ref对象
      txtRef = createRef()
      render() {
        return (
          <div id="app">
            <div>
              用户名:
              {/* 2.将创建好的ref对象添加到输入框上 */}
              <input name="username" type="text" ref={this.txtRef} />
              <button onClick={this.handleClick}>提交</button>
            </div>
          </div>
        )
      }
      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 (
      <div id="app">
        <h1>父组件</h1>
        {/* 1.子组件上绑定自定义属性 */}
        <Child msg={this.state.msg}></Child>
        <hr />
        <Son></Son>
      </div>
    )
  }
}
export default App

子组件–类组件

import { Component } from 'react'

class Child extends Component {
  render() {
    console.log(this.props)
    return (
      <div className="app-root">
        <h1>子组件</h1>
        {/* 2.在子组件中通过this.props来接收传递过来的数据 */}
        <div>{this.props.msg}</div>
      </div>
    )
  }
}

export default Child

子组件–函数组件

function Son(props) {
  return <div className="app-root">{props.msg}</div>
}

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

    class Child extends Component {
      // 如果使用了constructor构造函数,应该将props
      // 传递给super,否则无法在构造函数中获取到props
      constructor(props) {
        // 将props传递给super
        super(props)
        console.log(props, 'constructor')
      }
      render() {
        console.log(this.props)
        return (
          <div className="app-root">
            <h1>子组件</h1>
            {/* 2.在子组件中通过this.props来接收传递过来的数据 */}
            <div>{this.props.msg}</div>
          </div>
        )
      }
    }
    
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 (
      <div id="app">
        <h1>父组件</h1>
        {/* 1.子组件*/}
        <Child>
          <div>
            <h3>我是写在子组件标签元素内的元素</h3>
          </div>
        </Child>
      </div>
    )
  }
}
export default App

//子组件
import { Component } from 'react'

class Child extends Component {
  render() {
    console.log(this.props)
    return (
      <div className="app-root">
        <h1>子组件</h1>
        {/* 在子组件中通过this.props.children来接收写在子组件标签内的数据 */}
        <div>{this.props.children}</div>
      </div>
    )
  }
}

export default Child
通过父传子组件通信模拟vue中的具名插槽
//父组件
import { Component } from 'react'

// 引入子组件
import Child from './Child'
import Son from './Son'
class App extends Component {
  state = {
    msg: '我是父组件的数据',
  }
  render() {
    return (
      <div id="app">
        <h1>父组件</h1>
        {/* 1.在标签上绑定自定义属性,值为html标签*/}
        <Child
          left={<button>左边</button>}
          right={<button>右边</button>}
        ></Child>
      </div>
    )
  }
}
export default App

//子组件
import { Component } from 'react'

class Child extends Component {
  render() {
    console.log(this.props)
    return (
      <div className="app-root">
        <h1>子组件</h1>
        {this.props.left}
        <span>我是子组件中的内容</span>
        {this.props.right}
      </div>
    )
  }
}

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 (
      <div id="app">
        <h1>父组件</h1>

        <Child msg={true} arr={this.state.arr} person={this.state.obj}></Child>
      </div>
    )
  }
}
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 (
      <div className="app-root">
        <h1>子组件</h1>
        <span>{this.props.msg}</span>
      </div>
    )
  }
}

// 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 (
          <div className="app-root">
            <h1>子组件</h1>
            <span>{this.props.msg}</span>
          </div>
        )
      }
    }
    
    // 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 (
      <div id="app">
        <h1>父组件</h1>
        <div>{this.state.n}</div>
        {/*
       2.在子组件的标签上绑定一个自定义属性,
         值为定义的回调函数 
      */}
        <Child getChildData={this.getChildData}></Child>
      </div>
    )
  }
  // 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 (
      <div className="app-root">
        <h1>子组件</h1>
        <button onClick={this.handleClick}>点我</button>
      </div>
    )
  }
  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 (
      <div id="app">
        <h1>父组件</h1>
        <div>{this.state.n}</div>
        {/* 3.将回调函数传递给组件Child(组件A) */}
        <Child add={this.addN}></Child>
        <hr />
        <Son num={this.state.n}></Son>
      </div>
    )
  }
  // 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 (
      <div className="app-root">
        <h1>组件A</h1>
        <button onClick={this.handleClick}>n++</button>
      </div>
    )
  }
  handleClick = () => {
    // 4.在组件A中通过this.props来调用函数
    this.props.add(this.state.num)
  }
}

export default Child

组件B

function Son(props) {
  return (
    <div className="app-root">
      {/* 在组件B中通过props接收共享数据 */}
      <div>{props.num}</div>
    </div>
  )
}

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

export default MyContext

export { 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属性提供数据
      <Provider value="我是来自根组件的数据">
        <div id="app">
          <h1>父组件</h1>
          <Child></Child>
        </div>
      </Provider>
    )
  }
}
export default App

Son.js

import { Component } from 'react'
// 引入 MyContext对象
import MyContext from './bus'
class Son extends Component {
  // 3.指定contextType读取当前创建的context对象
  // static 定义静态属性
  static contextType = MyContext
  render() {
    return (
      <div className="app-root">
        <h1>Son组件</h1>
        {/* 通过this.context渲染 */}
        <div>{this.context}</div>
      </div>
    )
  }
}

export default Son

  • 如果是函数组件通过Consumer来渲染数据
// 函数组件通过Consumer来渲染数据

import { Consumer } from './bus'
function Son(props) {
  return (
    <div>
      <h1>Son组件</h1>
      <Consumer>
        {/* Consumer组件的子节点是一个函数,返回html,
         函数的参数就是Provider组件提供的数据
        */}
        {(data) => <button>{data}</button>}
      </Consumer>
    </div>
  )
}
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() //this
    console.log('constructor')
  }
  render() {
    console.log('render')
    return (
      <div id="app">
        <h1>AP片组件</h1>
      </div>
    )
  }
  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 (
      <div className="app-root">
        <h1>Child组件</h1>
        <button onClick={this.plus}>count+2</button>
        <hr />
        <div>
          {this.props.num}---{this.state.count}
        </div>
        <hr />
      </div>
    )
  }
  componentDidUpdate(prevProps, prevState) {
    // prevProps 更新之前的props值
    // prevState更新之前的state值
    // 我们可以对比更新之前的props和state和更新之后的props和state是否一致,
    // 来决定是否调用setState
    console.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() //this
    this.state = {
      num: 10,
      isshow: true,
    }
  }
  render() {
    return (
      <div id="app">
        <h1>AP片组件</h1>
        <button onClick={this.toggle}>切换</button>
        {this.state.isshow ? <Child num={this.state.num}></Child> : null}
        <button onClick={this.add}>更改num</button>
      </div>
    )
  }

  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 (
      <div className="app-root">
        <h1>Child组件</h1>
        <button onClick={this.plus}>count+2</button>
        <hr />
        <div>
          {this.props.num}---{this.state.count}
        </div>
        <hr />
      </div>
    )
  }
  componentDidUpdate(prevProps, prevState) {
    // prevProps 更新之前的props值
    // prevState更新之前的state值
    // 我们可以对比更新之前的props和state和更新之后的props和state是否一致,
    // 来决定是否调用setState
    console.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 (
      <div id="app">
        <List
          arrList={this.state.arrList}
          handleList={this.handleChange}
        ></List>
        <FooterCmp
          arrList={this.state.arrList}
          chooseAll={this.chooseAll}
        ></FooterCmp>
      </div>
    )
  }

  handleChange = (id) => {
    // 单选
    // console.log(id)
    const copyArrList = [...this.state.arrList]
    const idx = copyArrList.findIndex((item) => item.id === id)
    copyArrList[idx].checked = !copyArrList[idx].checked
    this.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 (
      <div>
        {this.props.arrList.map((item) => (
          <div key={item.id}>
            <input
              type="checkbox"
              checked={item.checked}
              onChange={(ev) => this.handleChange(item.id)}
            />
            {item.name}
          </div>
        ))}
      </div>
    )
  }
  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数组发生变化时返回最新的state

    return {
      checked: nextProps.arrList.every((item) => item.checked),
    }
  }

  render() {
    return (
      <div>
        全选:
        <input
          type="checkbox"
          checked={this.state.checked}
          onChange={this.changeChecked}
        />
      </div>
    )
  }

  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 (
      <div id="app">
        <button onClick={this.handleClick}>点我</button>
        <h1>父组件</h1>
        <div>{this.state.num}</div>
      </div>
    )
  }

  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 <div id="app"></div>
  }
}
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 (
      <div id="app">
        <button onClick={this.handleClick}>点我</button>
        <hr />
        <div style={{ padding: '0 10px' }}>{this.state.num}</div>
      </div>
    )
  }

  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(
  <React.StrictMode>
    {/* 将App组件用Router包裹起来 */}
    <Router>
      <App />
    </Router>
  </React.StrictMode>
)
4分别创建Home和Course组件
import { Component } from 'react'
export default class Course extends Component {
  render() {
    return <div>Home</div>
  }
}

import { Component } from 'react'
export default class Home extends Component {
  render() {
    return <div>Home</div>
  }
}

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 (
      <div id="app">
        <Route path="/home" component={Home}></Route>
        <Route path="/course" component={Course}></Route>
      </div>
    )
  }
}
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 (
      <div id="app">
        {/* 路由跳转
         Link模块的to属性可以是一个字符串,也可以是一个对象
        */}
        {/* <Link to="/home">首页</Link> */}
        <Link to={{ pathname: '/home' }}>首页</Link>
        <Link to="/course">课程</Link>

        <Route path="/home" component={Home}></Route>
        <Route path="/course" component={Course}></Route>
      </div>
    )
  }
}
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 (
      <div id="app">
        {/* 路由跳转*/}
        <NavLink activeClassName="active" to="/home">
          首页
        </NavLink>
        <NavLink activeClassName="active" to="/course">
          课程
        </NavLink>

        {/* 路由出口 */}
        <Route path="/home" component={Home}></Route>
        <Route path="/course" component={Course}></Route>
      </div>
    )
  }
}
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 (
      <div id="app">
        {/* 路由跳转*/}
        <NavLink activeClassName="active" to="/home">
          首页
        </NavLink>
        <NavLink activeClassName="active" to="/course">
          课程
        </NavLink>

        {/* 路由出口 */}
        <Route path="/home" component={Home}></Route>
        <Route path="/course" component={Course}></Route>
        {/* 访问根路径的时候,重定向到/home */}
        {/* from 从那个路径来  to 要跳转到那个路径 */}
        <Redirect from="/" to="/home"></Redirect>
      </div>
    )
  }
}
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 (
      <div id="app">
        {/* 路由跳转*/}
        <NavLink activeClassName="active" to="/home">
          首页
        </NavLink>
        <NavLink activeClassName="active" to="/course">
          课程
        </NavLink>

        {/* 将路由表用Switch组件包裹起来 */}
        <Switch>
          {/* 路由出口 */}
          <Route path="/home" component={Home}></Route>
          <Route path="/course" component={Course}></Route>
          {/* 访问根路径的时候,重定向到/home */}
          {/* from 从那个路径来  to 要跳转到那个路径 */}
          <Redirect from="/" to="/home"></Redirect>
        </Switch>
      </div>
    )
  }
}

解释:

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

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

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

模糊匹配和精确匹配

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

步骤
  • 定义一个NotFound页面

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

     {/* 将路由表用Switch组件包裹起来 */}
            <Switch>
              {/* 路由出口 */}
              <Route path="/home" component={Home}></Route>
              <Route path="/course" component={Course}></Route>
              {/* 访问根路径的时候,重定向到/home */}
              {/* from 从那个路径来  to 要跳转到那个路径 */}
              <Redirect from="/" to="/home"></Redirect>
              <Route component={NotFound}></Route>
            </Switch>
    

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

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

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

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

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

 {/* 将路由表用Switch组件包裹起来 */}
        <Switch>
          {/* 路由出口 */}
          <Route path="/home" component={Home}></Route>
          <Route path="/course" component={Course}></Route>
          {/* 访问根路径的时候,重定向到/home */}
          {/* from 从那个路径来  to 要跳转到那个路径 */}
          <Redirect from="/" to="/home" exact></Redirect>
          <Route component={NotFound}></Route>
        </Switch>
编程式导航
  • 声明式导航

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

  • 编程式导航

    通过js来实现路由的跳转

    export default class NotFound extends Component {
      render() {
        return (
          <div>
            <button onClick={this.handleClick}>点击去课程</button>
            <button onClick={() => this.props.history.goBack()}>返回</button>
          </div>
        )
      }
      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 (
      <div id="app">
        {/* 路由跳转*/}
        <NavLink activeClassName="active" to="/home">
          首页
        </NavLink>
        <NavLink activeClassName="active" to="/news">
          新闻
        </NavLink>
        <NavLink activeClassName="active" to="/course">
          课程
        </NavLink>

        <Switch>
          <Route path="/home" component={Home}></Route>
          <Route path="/course" component={Course}></Route>
          <Route path="/news" component={News}></Route>
          <Redirect from="/" to="/home" exact></Redirect>
          <Route component={NotFound}></Route>
        </Switch>
      </div>
    )
  }
}

课程下配置二级路由

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 (
      <div>
        <h1>Course</h1>
        <hr />
        <NavLink activeClassName="active" to="/course/js">
          js课程
        </NavLink>
        <NavLink activeClassName="active" to="/course/vue">
          vue课程
        </NavLink>
        <NavLink activeClassName="active" to="/course/react">
          react课程
        </NavLink>

        <Switch>
          <Route path="/course/js" component={JsCourse}></Route>
          <Route path="/course/vue" component={VueCourse}></Route>
          <Route path="/course/react" component={ReactCourse}></Route>
          <Redirect from="/course" to="/course/js"></Redirect>
        </Switch>
      </div>
    )
  }
}

动态路由

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

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

    {/* 通过 :id来声明我们要传递的参数是id */}
    <Route path="/detail/:id" component={Detail}></Route>
    
  • 路由匹配

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

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

    <Route path="/details" component={Details}></Route>
    
  • 路由匹配

    <ul>
              {this.state.arr.map((item) => (
                <li key={item.id}>
                  <NavLink to={'/details?id=' + item.id}>{item.tit}</NavLink>
                </li>
              ))}
            </ul>
    
  • 在组件中获取query方式传递的参数

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

    <Route path="/dstate" component={Dstate}></Route>
    
  • 路由匹配

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

    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 (
      <div>
        <h1>我是home组件的子组件</h1>
      </div>
    )
  }
}

Home.js

import { Component } from 'react'

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

import HomeChild from './HomeChild'
export default class Home extends Component {
  render() {
    return (
      <div>
        home
        <hr />
        {/* 将父组件的props传递给子组件 */}
        <HomeChild {...this.props}></HomeChild>
        <hr />
      </div>
    )
  }
}
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 (
          <div>
            <h1>我是home组件的子组件</h1>
          </div>
        )
      }
    }
    
    // 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 (
      <div id="app">
        {/* 路由跳转*/}
        <NavLink activeClassName="active" to="/home">
          首页
        </NavLink>
        <NavLink activeClassName="active" to="/news">
          新闻
        </NavLink>
        <NavLink activeClassName="active" to="/course">
          课程
        </NavLink>
        <Suspense fallback={<div>加载中...</div>}>
          {/* 将路由表用Switch组件包裹起来 */}
          <Switch>
            {/* 路由出口 */}
            <Route path="/home" component={Home}></Route>
            <Route path="/course" component={Course}></Route>
            <Route path="/news" component={News}></Route>

            {/* 通过 :id来声明我们要传递的参数是id */}
            <Route path="/detail/:id" component={Detail}></Route>

            <Route path="/details" component={Details}></Route>

            <Route path="/dstate" component={Dstate}></Route>
            {/* 访问根路径的时候,重定向到/home */}
            {/* from 从那个路径来  to 要跳转到那个路径 */}
            <Redirect from="/" to="/home" exact></Redirect>
            <Route component={NotFound}></Route>
          </Switch>
        </Suspense>
      </div>
    )
  }
}
路由守卫

登录拦截案例

  • 请求路径: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 (
      <div>
        <form>
          <div>
            <input
              type="text"
              name="username"
              value={this.state.username}
              autoComplete="off"
              onChange={(ev) => {
                this.handleChange(ev)
              }}
            />
          </div>
          <div>s
            <input
              type="password"
              value={this.state.password}
              onChange={(ev) => {
                this.handleChange(ev)
              }}
              name="password"
            />
          </div>
          <button type="button" onClick={this.login}>
            登录
          </button>
        </form>
      </div>
    )
  }

  // 受控组件
  handleChange = (ev) => {
    const el = ev.target
    this.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 (
      <div>
        <h1>首页面</h1>
        <button onClick={this.logout}>退出</button>
      </div>
    )
  }
  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 (
      <div id="app">
        <Suspense fallback={<div>加载中...</div>}>
          <Switch>
            <Route path="/Login" component={Login}></Route>
            {/* render属性来渲染组件 */}
            <Route
              path="/home"
              render={() =>
                this.loginVerfiy() ? <Home /> : <Redirect to="/login" />
              }
            ></Route>

            <Redirect from="/" to="/home" exact></Redirect>
            <Route component={NotFound}></Route>
          </Switch>
        </Suspense>
      </div>
    )
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值