前端开发

React 基本入门手册

概述

React 是一个用于构建用户界面的 JavaScript 库,将数据渲染为 HTML 视图的开源 Javascript 库。主要用来写 HTML 页面或构建 Web 应用。

为什么学习 React

  • 原生 JS 操作 DOM 繁琐、效率低(DOM-API 操作 UI)
  • 原生 JS 直接操作 DOM,浏览器会进行大量的重绘重排
  • 原生 JS 没有组件化编码方案,代码复用率低

React 的特点:

  • 声名式:只需要去描述 UI 看起来是什么样子,就跟写 HTML 一样;
  • 基于组件:组件是 React 中最重要的内容,组件表示页面中部分内容,通过组合、复用多个组件来完成这个页面;
  • 学习一次,随处使用:可以开发 Web 应用、移动端原生应用 (react-native)、虚拟现实应用等;
  • 使用虚拟 DOM 技术 + 优秀的 Diffing 算法,尽量减少与真实 DOM 的交互。

初始化环境

React 脚手架初始化项目

https://www.bilibili.com/video/BV1Z44y1K7Fj/?p=4

在终端中执行命令

// 初始化项目
npx create-react-app 项目名称

// 启动项目
npm start
  • npx-create-react-app 是固定命令,create-react-app 是脚手架名称
  • 后面跟随项目名称,项目名称可自定义
  • npx 命令会帮助我们临时安装 create-react-app 包,然后初始化项目完成之后会自动删除,所以不需要全局安装 create-react-app

项目目录

https://www.bilibili.com/video/BV1Z44y1K7Fj/?p=5

目录说明

  • src 目录是我们写代码进行项目开发的目录,为减少干扰,可只保留 App.js , index.css , index.js 三个文件即可,并且在文件中对代码进行精简(见下方)。
  • package.json 中两个核心库:react,react-dom

App.js

function App() {
  return (
    <div className="App">
      App
    </div>
  );
}

export default App;

index.css

index.js

// React: 框架核心包
// ReactDOM: 专门做渲染相关的包
import React from 'react';
import ReactDOM from 'react-dom/client';

// 应用的全局样式文件
import './index.css';

// 引入根组件
import App from './App';

// 渲染根组件 APP 到一个 id 为 root 的 DOM 节点
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <App />
);

JSX

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=7

JSX 是 JavaScript XML 的简写,是 JavaScript 的语法扩展,JSX 帮助我们在 JS 代码中直接书写 HTML 结构。

JSX是 React 中声明式编程的体现方式。声明式编程,简单理解就是以结果为导向的编程。使用 JSX 将我们所期望的网页结构编写出来,然后 React 再根据 JSX 自动生成 JS 代码。所以我们所编写的 JSX 代码,最终都会转为以调用 React.createElement() 创建元素的代码。

优势:

  • 采用类似于 HTML 的语法,降低学习成本,会 HTML 就会 JSX
  • 充分利用 JS 自身的可编程能力创建 HTML 结构

注:JSX 并不是标准的 JS 语法,是 JS 的语法扩展,浏览器默认是不识别的,脚手架内置的 @babel/plugin-transform-react-jsx 包用来解析该语法。

JSX 中使用 JavaScript 表达式

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=8

嵌入 JS 表达式

{ JS 表达式 }
// 常规变量
const name = 'PUJI'

Hello { name }

// 原生 JS 方法调用
const getAge = () => {
  return 36
}

{ getAge() } years

// 三元运算符
const flag = true
{ flag ? 'Yes' : 'No' }

可以使用的表达式

  • 字符串、数值、布尔值、null、undefined、object ( [] / {} )
  • 1 + 2 、 ‘abc’.split(”)、[‘a’, ‘b’].join(‘-‘)
  • fn()
  • 使用单花括号 {} 包裹 JS 表达式进行使用;
  • 单花括号中可以使用任意的 JavaScript 表达式;
  • JSX 自身也是一个 JS 表达式,所以也可以把 JSX 写入花括号中;
  • JS 中的对象是一个例外,一般只会出现在 style 属性中;
  • if 语句 / switch 语句 / 变量声明语句,这些叫语句,不是表达式,不能出现在 {} 中

JSX 的列表渲染

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=9

使用map映射的方式重复渲染

const colors = [
  {id: 1, name: 'red'},
  {id: 2, name: 'yellow'},
  {id: 3, name: 'blue'},
]

<ul>
  { colors.map(color => <li key={color.id}>{color.name}</li>)}
</ul>
  • 遍历列表时增加一个唯一的key值(number/string)来提高渲染性能。key 值不会在 DOM 结构中渲染,只会在虚拟 DOM 中使用

JSX 的条件渲染

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=10

if / else 或三元表达式或逻辑与运算符来实现

根据条件渲染特定的 JSX 结构

// 三元表达式
const flag = true

{ flag ? <span>Hello</span> : null }
// 更复杂的结构
{ flag ? (
  <div>
    <span>Hello</span>
  </div>
) : null }

// 逻辑与运算
{ true && <span>Hello</span> }

// if条件判断
const isLoading = true;
const loadData = () => {
  if(isLoading) {
    return <div>loading...</div>
  }
  return <div>loaded.</div>
}
  • 三元表达式中,如果模板中结构比较复杂,可以使用小括号进行包裹,在里面再进行换行

JSX 模板精简原则

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=11

模板中的逻辑尽量保持精简,复杂的多分支逻辑,收敛为一个函数,通过一个专门的函数来写分支逻辑,模板中只负责调用。

const getHtag = type => {
  if(type === 1) {
    return <h1>this is h1</h1>
  }
  if(type === 2) {
    return <h2>this is h2</h2>
  }
  if(type === 3) {
    return <h3>this is h3</h3>
  }
}

{ getHtag(1) }

JSX 的样式处理

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=12

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=13

行内样式

在元素身上绑定一个 style 属性即可

style= {{...}}
const title = (
  <h1 style={ {color: 'blue', backgroundColor: 'yellow'} }>PUJI</h1>
)

// 优化写法
const style = {
  color: 'blue',
  backgroundColor: 'yellow'
}
<h1 style={ style }>PUJI</h1>

类名样式

在元素自身增加 className 属性

const title = (
  <h1 className="title">PUJI</h1>
)

引入 CSS 文件

import './css/style.css'

动态类名控制

const flag = true
<h1 className={ flag ? 'title' : '' }>PUJI</h1>

JSX 注意事项

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=14

  • JSX 必须有一个根节点,如果没有根节点,可以使用 <></> (幽灵节点) 替代
  • 所有标签必须形成闭合,成对闭合或者自闭合都可以
  • JSX 中的语法更贴近 JS 语法,属性名采用驼峰命名法 class -> className / for -> htmlFor
  • JSX 支持多行(换行),如果需要换行,需使用 () 包裹,防止出现bug

开发工具配置

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=15

基于 VS Code 配置格式化工具,提高开发效率,安装 VS Code prettier 插件

组件

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=17

目标

  • 能够使用函数创建组件
  • 能够使用 class 创建组件
  • 能够给 React 元素绑定事件
  • 能够使用 state 和 setState()
  • 能够处理事件中的 this 指向问题
  • 能够使用受控组件方式处理表单

组件概念

将不同的功能抽离进行封装,在页面中将组件组合在一起形成一个页面。

函数组件

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=18

使用 JS 的函数或箭头函数创建的组件

// 创建函数组件
function Hello() {
  return <div>我的第一个函数组件</div>
}

// 渲染 (自闭合形式)
<Hello/>

// 渲染 (成对闭合形式)
<Hello></Hello>
  • React 中的函数组件的书写格式和 JS 函数完全一致
  • React 中的函数组件的函数名称必须以大写字母开头
  • React 中的函数组件必须有返回值,表示该组件的 UI 结构
  • 如果返回值为 null,表示不渲染任何内容
  • 渲染函数组件,使用函数名作为组件的标签名的方式,标签可以是单标签或双标签形式

类组件

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=19

使用 ES6 的 class 创建的组件

// 创建类组件
import React from "react";

class Hello extends React.Component {
  render() {
    return <div>Hello</div>
  }
}

// 渲染 (自闭合形式)
<Hello/>

// 渲染 (成对闭合形式)
<Hello></Hello>
  • React 中的类组件类名称必须以大写字母开头
  • React 中的类组件应该继承 React.Component 父类,从而可以使用父类中提供的属性和方法
  • React 中的类组件必须提供 render() 方法
  • render() 方法必须有返回值,表示该组件的结构
  • 如果返回值为 null,表示不渲染任何内容
  • 渲染类组件,使用类名作为组件的标签名的方式,标签可以是单标签或双标签形式

抽离组件

把每一个组件抽离为独立的 JS 文件

  1. 创建组件 js 文件
  2. 在组件文件中导入 React
  3. 创建组件 (函数组件或类组件)
  4. 导出该组件
  5. 在使用该组件的 JS 文件中导入对应的组件
  6. 渲染组件
// Hello.js
import React from 'react';
class Hello extends React.Component {
    render() {
        return <div>我的第一个组件库</div>
    }
}
export default Hello

// 导入组件并渲染
import Hello from './Hello'
<Hello/>

数组操作

React 中常用的数组操作方法有

map()

filter()

concat()

事件

事件绑定

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=20

React 事件绑定语法与 DOM 事件语法相似

on+事件名称 = {事件处理程序}

onClick = {() => {
  ...
}}
  • React 事件采用驼峰命名法,比如 onMouseEnter, onFocus
// 函数组件形式
function Hello() {
  function handleClick() {
    console.log('Clicked')
  }
  return <button onClick={handleClick}>Click</button>
}

<Hello />

// 类组件形式
class Hello extends React.Component {
  handleClick = () => {
    console.log('Clicked')
  }
  render() {
    return <button onClick={this.handleClick}>Click</button>
  }
}

<Hello />

事件对象e

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=21

可以通过事件处理程序的参数获取到事件对象,React 中的事件对象叫做合成对象或合成对象事件,合成对象的特点是兼容所有的浏览器。

function Hello() {
  const handleClick = (e) => {
    e.preventDefault()
    console.log('Clicked', e)
  }
  return <a href="https://puji.design" onClick={handleClick}>Click</a>
}

<Hello />

事件对象中传递自定义参数

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=22

function Hello() {
  const handleClick = (msg) => {
    console.log('Clicked', msg)
  }
  return <button onClick={() => handleClick('this is msg')}>Click</button>
}

<Hello />


// 综合方式
function Hello() {
  const handleClick = (e, msg) => {
    console.log('Clicked', e, msg)
  }
  return <button onClick={(e) => handleClick(e, 'this is msg')}>Click</button>
}

<Hello />
  • 在调用时采用箭头函数的方法,可以将传递的参数改造为实参

抽离事件处理程序

JSX 中参杂过多 JS 逻辑代码,会显得非常混乱,推荐将逻辑抽离到单独的方法中,保证 JSX 结构清晰。

import React from 'react';

class Hello extends React.Component {
  // ES6 简化语法初始化状态
  state = {
    count: 0
  }
  changeCount = () => {
    this.setState({
      count: this.state.count + 1
    })
  }
  render() {
    return (
      <div>
        <h1>计数器: { this.state.count }</h1>
          <button onClick={ () => this.changeCount() }>+1</button>
      </div>
    )
  }
}

事件绑定的 this 指向

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=26

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=27

class 实例方法 (规范写法)

class Hello extends React.Component {
  state = {
    count: 0
  }
  changeCount = () => {
    this.setState({
      count: this.state.count + 1
    })
  }
  render() {
    return (
      <div>
        <h1>计数器: { this.state.count }</h1>
          <button onClick={ this.changeCount }>+1</button>
      </div>
    )
  }
}

<Hello />

箭头函数

利用箭头函数自身不绑定 this 的特点

class Hello extends React.Component {
  state = {
    count: 0
  }
  changeCount() {
    this.setState({
      count: this.state.count + 1
    })
  }
  render() {
    return (
      <div>
        <h1>计数器: { this.state.count }</h1>
          <button onClick={ () => this.changeCount() }>+1</button>
      </div>
    )
  }
}

<Hello />

bind() 方法(旧版)

利用 bind() 方法,将事件处理程序中的 this 与组件实例绑定到一起

class Hello extends React.Component {
  state = {
    count: 0
  }
  constructor() {
    super()
    this.onIcrement = this.onIcrement.bind(this)
  }
  onIcrement() {
    this.setState({
      count: this.state.count + 1
    })
  }
  render() {
    return (
      <div>
        <h1>计数器: { this.state.count }</h1>
        <button onClick={ this.onIcrement }>+1</button>
      </div>
    )
  }
}

<Hello />

组件状态 (State)

状态即数据,是组件内部的私有数据,只能在组件内部使用。函数组件又叫做无状态组件,只负责数据展示(静态)。类组件又叫做有状态组件,负责更新 UI,让页面 “动” 起来。如果需要让用户在操作页面后得到反馈,则必须使用类组件。

类组件使用

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=23

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=24

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=25

1、初始化状态

通过 class 的实例属性 state 来初始化

class Counter extends React.Component {
  // 初始化状态
  state = {
    // 在这里可以定义各种属性,全是当前组件的状态
    count: 0
  }
  render() {
    // 使用状态
    return <button>Counter { this.state.count }</button>
  }
}

2、读取状态

import React from "react";

class Counter extends React.Component {
  state = {
    count: 0
  }
  render() {
    return <button>Counter{ this.state.count }</button>
  }
}

3、修改状态并影响视图

在 React 中不可以直接做赋值修改,必须通过一个方法 setState 来处理

import React from "react";

class Counter extends React.Component {
  state = {
    count: 0
  }
  changeCount = () => {
    this.setState({
      count: this.state.count + 1
    })
  }
  render() {
    return <button onClick={ () => this.changeCount() }>Counter{ this.state.count }</button>
  }
}

注意事项

  • 编写组件其实就是编写原生js类或函数
  • 定义状态必须通过 state 实例属性的方法提供一个对象,表示一个组件中可以有多个数据。名称是固定的就叫 state
  • 在 React 中的数据不可直接赋值,必须使用 setState 方法实现,这个方法来自继承的 React.Component 得到
  • 这里的this关键词,很容易出现指向错误问题。

多种类型状态修改

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=29

state = {
  count: 0,
  list: [1,2,3],
  person: {
    name: 'PUJI',
    age: 36
  }
}

this.setState({
  count: this.state.count + 1,
  list: [...this.state.list, 4],
  person: {
    ...this.state.person,
    // 覆盖原来的属性就可以达到修改对象中属性的目的
    name: 'wewe'
  }
})

// 数组数据的删除,可以使用filter
this.setState({
  // 删除2
  list: this.state.list.filter(item => item !== 2),
})
  • state 中尽量保持精简
  • 如果数据是组件的状态,需要去影响视图,则定义到 state 中
  • 如果数据状态不和视图绑定,定义成一个普通的实例属性即可

表单处理

目标:

  • 能够使用受控组件的方式获取文本框的值

使用 React 处理表单元素,一般有两种方法:

  • 受控组件(推荐)
  • 非受控组件

受控表单组件

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=30

受控组件就是可以被 React 的状态控制的组件。React 组件的状态的地方是在 state 中,input 表单元素也有自己的状态是在 value 中,React 将 state 与表单元素的值(value)绑定到一起,由 state 的值来控制表单的值,从而保证单一数据源的特性

实现步骤

以获取文本框的值为例,受控组件的使用步骤如下:

  1. 在组件的 state 中声明一个组件的状态数据
  2. 将状态数据设置为 input 标签元素的 value 属性的值
  3. 为 input 添加 change 事件
  4. 在事件处理程序中,通过事件对象 e 获取到当前文本框的值(即用户当前输入的值)
  5. 调用 setState 方法,将文本框的值作为 state 状态的最新值
class Input extends React.Component {
  state = {
    message: 'PUJI Design'
  }
  inputChange = e => {
    this.setState({
      message: e.target.value
    })
  }
  render() {
    return (
      <input
        type='text'
        value={this.state.message}
        onChange={this.inputChange}
      />
    )
  }
}

<Input/>

非受控组件(不推荐)

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=31

非受控组件就是通过手动操作 DOM 的方式获取文本框的值,文本框的状态不受 React 组件的 state 中的状态控制,直接通过原生 DOM 获取输入框的值。

实现步骤

  • 导入 createRef 函数
  • 调用 createRef 函数,创建一个 ref 对象,存储到名为 msgRef (名称可自定义)的实例属性中
  • 为 input 添加 ref 属性,值为 msgRef
  • 在按钮的事件处理程序中,通过 msgRef.current 即可拿到 input 对应的 DOM 元素,而其中 msgRef.current.value 拿到的就是文本框的值
import React, {createRef} from "react";

class Input extends React.Component {
  msgRef = createRef()
  getValue = () => {
    // 通过 msgRef 获取 input value
    console.log(this.msgRef.current.value);
  }
  render() {
    return (
      <>
        <input
          type='text'
          ref={this.msgRef}
        />
        <button onClick={this.getValue}>Get value</button>
      </>
    )
  }
}

<Input/>

组件通信

概念

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=37

组件是独立且封闭的单元,默认情况下组件只能使用自己的数据(state)

组件化开发的过程中,完整的功能会拆分多个组件,在这个过程中不可避免的需要互相传递一些数据为了能让各组件之间可以进行互相沟通,数据传递这个过程就是组件通信。

组件之间的关系

  • 父子关系 – 最重要
  • 兄弟关系 – 自定义事件模式产生技术方法 eventBus / 通过共同的父组件通信
  • 其他关系 – mobx / redux / 基于 hook 的方案

父传子

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=38

实现步骤

  1. 父组件提供要传递的数据 – state
  2. 给子组件标签添加属性值为 state 中的数据
  3. 子组件中通过 props 接收父组件中传过来的数据
    1. 类组件使用 this.props 获取 props 对象
    2. 函数组件直接通过参数获取 props对象
import React from "react";

function SonF(props) {
  return (
    <div>我是函数组件, {props.msg}</div>
  )
}

class SonC extends React.Component {
  render() {
    return (
      <div>我是类组件, {this.props.msg}</div>
    )
  }
}

class App extends React.Component {
  state = {
    message: 'PUJI Design'
  }
  render() {
    return (
      <div>
        {/* 子组件身上绑定属性,属性名可以自定义 */}
        <SonF msg={ this.state.message } />
        <SonC msg={ this.state.message } />
      </div>
    )
  }
}

子传父

https://www.bilibili.com/video/BV1Z44y1K7Fj/?p=41

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=42

子组件调用父组件传递过来的函数,并且把想要传递的数据当成函数的实参传入即可

import React from "react";

function Son(props) {
  const { getSonMsg } = props
  return (
    <div>
      this is son<button onClick={() => getSonMsg('来自子组件')}>click</button>
    </div>
  )
}

class App extends React.Component {
  getSonMsg = (sonMsg) => {
    console.log(sonMsg);
  }
  render() {
    return (
      <div>
        <Son getSonMsg={this.getSonMsg}/>
      </div>
    )
  }
}

props 说明

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=39

  • props 是只读对象,根据单项数据流的要求,子组件只能读取 props 中的数据,不能修改
  • props 可以传递任意数据(数字、字符串、布尔值、数组、对象、函数、JSX)
import React from "react";

function Son(props) {
  return (
    <div>
      我是函数组件, {props.list.map(item => <p key={item}>{item}</p>)}
      {props.userInfo.name}
      <button onClick={props.getMes}>click</button>
      {props.child}
    </div>
  )
}

class App extends React.Component {
  state = {
    list: [1,2,3],
    userInfo: {
      name: 'PUJI',
      age: 36
    }
  }
  getMes = () => {
    console.log('父组件中的函数');
  }
  render() {
    return (
      <div>
        <Son
          list={ this.state.list }
          userInfo={ this.state.userInfo }
          getMes={this.getMes}
          child={<span>this is span</span>}
        />
      </div>
    )
  }
}

props 解构赋值

https://www.bilibili.com/video/BV1Z44y1K7Fj/?p=40

import React from "react";

function Son(props) {
  const {list, userInfo, getMes, child} = props
  return (
    <div>
      我是函数组件, {list.map(item => <p key={item}>{item}</p>)}
      {userInfo.name}
      <button onClick={getMes}>click</button>
      {child}
    </div>
  )
}

class App extends React.Component {
  state = {
    list: [1,2,3],
    userInfo: {
      name: 'PUJI',
      age: 36
    }
  }
  getMes = () => {
    console.log('父组件中的函数');
  }
  render() {
    return (
      <div>
        <Son
          list={ this.state.list }
          userInfo={ this.state.userInfo }
          getMes={this.getMes}
          child={<span>this is span</span>}
        />
      </div>
    )
  }
}
import React from "react";

function Son({list, userInfo, getMes, child}) {
  return (
    <div>
      我是函数组件, {list.map(item => <p key={item}>{item}</p>)}
      {userInfo.name}
      <button onClick={getMes}>click</button>
      {child}
    </div>
  )
}

class App extends React.Component {
  state = {
    list: [1,2,3],
    userInfo: {
      name: 'PUJI',
      age: 36
    }
  }
  getMes = () => {
    console.log('父组件中的函数');
  }
  render() {
    return (
      <div>
        <Son
          list={ this.state.list }
          userInfo={ this.state.userInfo }
          getMes={this.getMes}
          child={<span>this is span</span>}
        />
      </div>
    )
  }
}

兄弟组件通信

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=43

通过状态提升机制,利用共同的父组件实现兄弟通信

实现步骤:

  1. 将需要传递出数据的子组件的数据传向父级组件
  2. 在父级组件中保存数据
  3. 在父级组件中将数据传递到需要接收数据的子组件
import React from "react";

function SonA(props) {
  return (
    <div>this is A, {props.sendAMsg}</div>
  )
}

function SonB(props) {
  const BMsg = 'PUJI Design'
  return (
    <div>this is B <button onClick={() => props.getBMsg(BMsg)}>click</button></div>
  )
}

class App extends React.Component {

  state = {
    sendAMsg: 'init'
  }

  getBMsg = (BMsg) => {
    this.setState({
      sendAMsg: BMsg
    })
  }

  render() {
    return (
      <div>
        <SonA sendAMsg={this.state.sendAMsg}/>
        <SonB getBMsg={this.getBMsg}/>
      </div>
    )
  }
}

跨组件通信 Context

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=44

当组件的层级过多时,如果我们使用 props 逐层传递数据显然很繁琐,那么 Context 提供了一个无需为每层组件手动添加 props , 就能在组件树间进行数据传递的方法

实现步骤:

  1. 调用createContext方法,导出 Provider 和 Consumer 对象
  2. 使用 Provider 包裹根组件提供数据
  3. 需要用到数据的组件使用 Consumer 包裹获取数据
const { Provider, Consumer } = createContext()

<Provider value={this.state.message}>
  {/* 根组件 */}
</Provider>

<Consumer>
  {value => /* 基于 Context 值进行渲染 */}
</Consumer>
import React, { createContext } from "react";

const { Provider, Consumer } = createContext()
function ComA () {
  return (
    <>
      <div>this is comA</div>
      <ComB />
    </>
  )
}

function ComB () {
  return (
    <div>
      this is comB
      <Consumer>
        {value => <span>{value}</span>}
      </Consumer>
    </div>
  )
}

class App extends React.Component {
  state = {
    message: 'PUJI Design'
  }
  render() {
    return (
      <Provider value={this.state.message}>
        <div>
          <ComA />
        </div>
      </Provider>
    )
  }
}

组件进阶

children属性

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=46

表示该组件的子节点,只要组件内部由子节点,props 中就有该属性,children也可以使用map进行遍历。

children可以时什么

  • 普通文本
  • 普通标签元素
  • 函数
  • JSX
import React, { createContext } from "react";

function ComTest ({ children }) {
  return (
    <div>this is ComTest, {children}</div>
  )
}

class App extends React.Component {
  render() {
    return (
      <div>
        <ComTest>
          {<div>this is child</div>}
        </ComTest>
      </div>
    )
  }
}

prop 默认值

通过 defaultProps 可以给组件的 props 设置默认值,在未传入 props 的时候生效

1、函数组件

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=49

使用 defaultProps(弃用):

function List(props) {
  return (
    <div>
      此处展示props的默认值, {props.pageSize}
    </div>
  )
}

// 设置默认值
List.defaultProps = {
  pageSize: 10
}

// 不传入pageSize属性
<List />

使用函数参数默认值(推荐):

// 新版 React 已经不在推荐使用defaultProps 来给函数组件添加默认值,而推荐函数参数默认值来实现
function List( {pageSize = 10} ) {
  return (
    <div>
      此处展示props的默认值, {pageSize}
    </div>
  )
}

// 不传入pageSize属性
<List />

2、类组件

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=50

使用 defaultProps:

class List extends React.Component {
  render() {
    return (
      <div>
        此处展示props的默认值,{this.props.pageSize}
      </div>
    )
  }
}

// 设置默认值
List.defaultProps = {
  pageSize: 10
}

// 不传入pageSize属性
<List />

使用类静态属性声明:

class List extends React.Component {
  static defaultProps = {
    pageSize: 10
  }
  render() {
    return (
      <div>
        此处展示props的默认值,{this.props.pageSize}
      </div>
    )
  }
}

// 不传入pageSize属性
<List />

类组件生命周期

概念

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=51

组件的生命周期是指组件从被创建到挂载到页面中运行起来,再到组件不用时卸载的过程,注意,只有类组件才有生命周期。

挂载阶段

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=52

钩子函数触发时机作用
constructor(不建议)创建组件时,最先执行,初始化的时候只执行一次1. 初始化 state
2. 创建 Ref
3. 使用 bind 解决 this 指向问题等
render每次组件渲染都会触发渲染 UI(注:不能再里面调用 setState() )
componentDidMount组件挂载(完成DOM渲染)后执行,只在初始化的时候执行一次1. 发送网络请求
2. DOM 操作
import React, { createContext } from "react";

class App extends React.Component {
  constructor() {
    super();
    console.log('constructor');
  }
  componentDidMount() {
    console.log('componentDidMount');
  }
  render() {
    console.log('render');
    return (
      <div>
        this is App
      </div>
    )
  }
}

更新阶段

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=53

钩子函数触发时机作用
render每次组件渲染都会触发渲染 UI (与挂载阶段相同)
componentDidUpdate组件更新后 (DOM 渲染完毕)DOM 操作,可以获取到更新后的DOM内容,不要直接调用 setState

卸载时

钩子函数触发时机作用
componentWillUpdate组件卸载 (从页面中消失)执行清理工作 (如清理定时器等)

hooks(函数组件)

概念

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=60

https://www.bilibili.com/video/BV1wZ4y1U7vx?p=4

React 体系里组件分为类组件和函数组件,经过多年的实战,函数组件是一个更加匹配 React 的设计理念 UI = f(data),也更有利于逻辑拆分与重组的组件表达形式,而先前的函数组件是不可以有自己的状态的,为了能让函数组件可以拥有自己的状态,所以从 react v16.8 开始,hooks应运而生。

hooks的本质:一套能够使函数组件更强大,更灵活的钩子

注意点:

  • 有了hooks之后,为了兼容老版本,class类组件并没有移除,两者都可使用
  • 有了hooks之后,不能在把函数称为无状态组件了,因为hooks为函数组件提供了状态
  • hooks只能在函数组件中使用

hooks解决了什么问题:

hooks的出现解决了两个问题:

  • 组件的逻辑复用
  • class组件自身的问题

hooks的优势总结:

  • 告别难以理解的class
  • 解决业务逻辑难以拆分的问题
  • 使状态逻辑复用变得简单可行
  • 函数组件在设计思想上,更契合 React 的理念

useState

基本使用

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=61

useState 为函数组件提供状态 (state)

使用步骤:

  1. 导入 useState 函数
  2. 调用 useState 函数,并传入状态的初始值
  3. 从 useState 函数的返回值中,拿到状态和修改状态的方法
  4. 在 JSX 中展示状态
  5. 调用修改状态的方法更新状态
import { useState } from "react";
function App() {
  const [count, setCount] = useState(0)
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>{count}</button>
    </div>
  )
}

数据的读取与修改:

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=62

  • useState 传过来的参数作为 count 的初始值
  • [ count, setCount ] 的写法是一个结构赋值,useState返回值是一个数组,数组中的名称可以自定义,顺序不可调换,第一个参数是数据状态,第二个参数是修改数据的方法
  • 第二个参数是一个函数,作用是用来修改第一个参数的值,依旧保持不能直接修改原值,使用这个函数生成一个新值替换原值
  • 两个参数是一对且绑定在一起,第二个参数只能修改同一个数组中的第一个参数

回调函数参数:

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=69

参数只会在组件的初始渲染中起作用,后续渲染时会被忽略,如果初始 state 需要通过计算才能获得,则可以使用一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用。

const [name, setName] = useState(() => {//计算逻辑 return '计算之后的初始值'})
  • 回调函数 return 出去的值将作为 name 的初始值
  • 回调函数中的逻辑只会在组件初始化的时候执行一次

组件的更新过程:

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=63

  • 首次渲染时,组件内部的代码会被执行一次,其中 useState 也会跟着执行,初始值仅在首次渲染时生效
  • 每次调用第二个参数的方法时,useState会再次执行,得到新的值,组件会再次被执行

使用规则:

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=64

  • useState 函数可以在一个函数内使用多次,每个都互相独立,每调用一次为函数组件提供一个新的状态
  • useState 只能出现在函数组件中
  • useState 只能在函数组件的最外层
  • useState 不能嵌套在 if / for / 其他函数中(React 按照 hooks 的调用顺序识别每一个hook )
  • hooks 可以通过 React 调试工具查看
  • 函数组件内,每次状态的更新,整个函数将会重新执行一次,useState 只会在组件第一次渲染时使用状态的初始值,之后会使用状态更新后的值。同一个函数多次渲染之间,state是共享的

useEffect

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=65

useEffect 函数是为 React 函数组件提供副作用的处理。

什么是副作用:

副作用是相对于主作用来说的,一个函数除了主作用,其他的作用就是副作用。对于 React 组件来说,主作用是根据数据 (state/props) 渲染UI,除此之外都是副作用(比如,手动修改 DOM)

常见的副作用:

  • 数据请求 ajax 发送
  • 手动修改 DOM
  • localstorage 操作

基本使用:

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=65

  1. 导入 useEffect 函数
  2. 在函数组件中执行,传入回调(函数),并且定义副作用
  3. 当我们通过修改状态更新组件时,副作用也会不断执行
useEffect(副作用函数,[依赖项])
import { useState, useEffect } from "react";
function App() {
  const [count, setCount] = useState(0)
  useEffect(() => {
    document.title = count
  })
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>{count}</button>
    </div>
  )
}

执行时机:

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=66

  • 默认状态(无依赖项):组件初始化时先执行一次,每更新一次再次执行
  • 添加空数组后,组件只会在首次渲染时执行一次
  • 添加特定依赖项,副作用在首次渲染时执行,在依赖项发生变化时重新执行
  • useEffect 回调函数中用到的数据就是依赖数据,就应该出现在依赖项数组中,如果不添加依赖项可能会有bug出现。
  • 如果有多个依赖项时,只要有一个依赖项有变化时,都会重新执行
  • 副作用的依赖项即是组件状态的值
  • useEffect 传入的函数不可使用异步函数,如有异步操作,可在该函数内再设置一个异步函数
// 添加空数组
useEffect(() => {
  console.log('PUJI Design')
},[])

// 添加依赖项
useEffect(() => {
  console.log('PUJI Design')
},[count])

清理副作用

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=70

清理副作用会在组件卸载时,或组件下一次调用之前执行。

import { useEffect } from "react";
useEffect(() => {
  console.log('副作用的函数执行了')
  return () => {
    cosole.log('清理副作用的函数执行了')
  }
})

发送网络请求

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=71

useEffect( () => {
  async function loadData () {
    const res = await fetch('https://puji.design')
    console.log(res)
  }
  loadData()
},[])

useRef

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=72

在 React 中进行 DOM 操作,获取 DOM 元素,也可以在多次渲染之间共享数据

使用步骤:

  • 导入 useRef 函数
  • 执行 useRef 函数并传入 null,返回值为一个对象内部有一个 current 属性存放拿到的 DOM 对象(组件实例)
  • 通过 ref 绑定要获取的元素或者组件
import {useEffect, useRef} from 'react'
function App() {
  const h1Ref = useRef(null)
  useEffect(() => {
    console.log(h1Ref)
  },[])
  return (
    <div><h1 ref={ h1Ref }>this is h1</h1></div>
  )
}
  • useRef 的初始值为 null

多次渲染时共享数据

  • 导入 useRef
  • 调用 useRef
  • 向引用的 current 中存入数据
import {useEffect, useRef} from 'react'
function App() {
  const refTimeID = useRef(null)
  const [count, setCount] = useState(0)

  useEffect(() => {
    refTimeID.current = setInterval(() => {
      setCount((count) => count + 1)
    }, 1000)
  },[])

  const hClick = () => {
    clearInterval(timeID)
  }
  return (
    <div><h1 ref={ h1Ref }>this is h1</h1></div>
  )
}

useContext

全局状态,跨组件通信

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=73

https://www.bilibili.com/video/BV1SP4y1K76h?p=15

  • 导入并调用 createContext 方法,得到 Context 对象并导出
  • 使用 Provider 组件包裹根目录,并通过 value 属性提供要共享的数据
  • 在任意后代入组件中,如果希望获取公共数据(导入 useContext,调用 useContext(第一步导入的 Context ) 得到 value 值
// 根组件
import { createContext } from 'react'
import Father from '父组件文件路径'
export const Context = createContext()

const App = () => {
  return (
    <Context.Provider value={ 传递的数据 }>
      <Son />
    </Context.Provider>
  )
}

// 父组件
import Son from '子组件文件路径'
const Father = () => {
  return <Son />
}

// 子组件
import { useContext } from 'react'
import { Context } from '根组件文件路径'
const Son = () => {
  const res = useContext(Context)
  return (
    <div>{res}</div>
  )
}

自定义hooks

同样的逻辑复用

https://www.bilibili.com/video/BV1SP4y1K76h?p=7

  • 自定义 Hooks 是一个函数,在 React 中自定义 Hooks 名称需使用 use 开头
  • 自定义 Hooks 只能在函数组件中火其他自定义 Hooks 中使用
  • 自定义 Hooks 用来提取组件的状态逻辑,根据不同功能可以有不同的参数和返回值(和普通函数一致)
import { useState, useEffect } frome 'react'
export default function useMousePosition() {
  const [position, setPosition] = useState({x: 100, y: 100})
  // 使用副作用,且仅在组件挂载时使用,组件卸载时清理
  useEffect( () => {
    const fn = e => {
      setPosition({x: e.x, y: e.y})
    }
    document.addEventListner('mousemove', fn)
    return () => {
      document.removeEventListner('mousemove', fn)
    }
  }, [])
  return position
}

注意事项

  • 在 useEffect 的回调函数中使用的变量,都必须在依赖项中声明
  • Hooks 不能出现在条件语句或循环中,也不能出现在return之后
  • Hooks 只能在函数组件或自定义 Hooks 中使用

插件

uuid

可生成独一无二的标识 id 值

// 安装
npm add uuid

// 使用
import {v4 as uuid} from 'uuid'
uuid()

PropTypes 数据类型校验

https://www.bilibili.com/video/BV1Z44y1K7Fj?p=47

https://reactjs.org/docs/typechecking-with-proptypes.html

对于组件来说,props 是由外部传入的,我们其实无法保证组件使用者传入了什么格式的数据,如果传入的数据格式不对,就有可能会导致组件内部错误,有一个点很关键 – 组件的使用者可能报错了也不知道为什么。面对这样的场景,我们就需要使用 props 校验。

实现步骤:

  • 安装属性校验包 npm add prop-types
  • 导入 prop-types
  • 使用 组件名.propTypes = {} 给组件添加校验规则
import PropTypes from 'prop-types'

const List = props => {
  const arr = props.colors
  const lis = arr.map((item, index) => <li key={index}>{item.name}</li>)
  return <ul>{lis}</ul>
}

List.propTypes = {
  colors: PropTypes.array
}

四种常见结构:

  • 常见类型:array, bool, func, number, object, string
  • React 元素类型:element
  • 必填项:isRequired
  • 特定的结构对象:shape({})

核心代码:

// 常见类型
optionalFunc: PropTypes.func

// 必填
requiredFunc: PropTypes.func.isRequired

// 特定结构的对象
optionalObjectWithShape: PropTypes.shape({
  color: PropTypes.string
  fontSize: PropTypes.number
})

eslint-plugin-react-hooks

开发依赖

https://www.bilibili.com/video/BV1SP4y1K76h?p=20

// 安装
npm install eslint-plugin-react-hooks --save-dev

// 修改配置文件,加入两个规则
{
  "plugins": [
    "react-hooks"
  ],
  "rules": {
    // 检查 Hooks 的使用规则
    "react-hooks/rules-of-hoooks": "error",
    // 检查依赖项的声明
    "react-hooks/exhaustive-deps": "warn"
  }

参考链接

文档

https://reactjs.org

https://react.docschina.org

https://zh-hans.reactjs.org

https://beta.reactjs.org

Bilibili 视频教程

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

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

朴及设计 PUJI Design (c) 2023. 网站由 摩块 MooKwai 智能网站生成器生成。