React基础知识总结

1、React 概述

1.1、什么是 React
  • React 是一个用于构建用户界面的 JavaScript 库
  • React 主要用来写 HTML 页面,或构建 Web 应用
  • 如果从 MVC 的角度来看,React 仅仅是视图层(V),也就是指负责视图的渲染,而并非提供了完整的 M 和 C 的功能
  • React 起源于 Fecebook 的内部项目,后来用来架设 Instagram 的网站,并于 2013 年 5 月开源
1.2、React 的特点
  • 声明式
  • 基于组件
  • 学习一次,随处使用
1.2.1 声明式

你只需要描述 UI(HTML)看起来是什么样,就跟写 HTML 一样,React 负责渲染 UI,并在数据变化时更新 UI

const jsx = <div className="app"><h1>Hello React!动态变化的数据:{ count }</h1></div>
1.2.2 基于组件
  • 组件是 React 最重要的内容
  • 组件表示页面中的部分内容
  • 组合、复用多个组件,可以实现完整的页面功能
1.2.3 学习一次,随处使用
  • React 可以开发 Web 应用
  • React 可以开发移动端原生应用(React-Native)
  • React 可以开发 VR(虚拟现实)应用(React 360)

2、React 的基本使用

2.1、React 的安装
  • 安装命令:npm i react react-dom
  • react 包是核心,提供创建元素、组件等功能
  • react-dom 包提供了 DOM 相关功能等
2.2、React 的使用
  1. 引入 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>
  1. 创建 React 元素
  2. 渲染 React 元素到页面中
<div id="root"></div>
<script>
  const title = React.createElement('h1', null, 'Hello React')
  ReactDOM.render(title, document.getElementById('root'))
</script>
2.3、React 方法说明
  • React.createElement() 说明
# 返回值:React 元素
# 第一个参数:要创建的 React 元素名称
# 第二个参数:该 React 元素的属性
# 第三个即其以后的参数:该 React 元素的子节点
const el = React.createElement('h1', { title: '标题' }, 'Hello React')
  • ReactDOM.render() 说明
# 第一个参数:要渲染的 React 元素
# 第二个参数:DOM 对象,用于指定渲染到页面中的位置
ReactDOM.render(el, document.getElementById('root'))

3、React 脚手架的使用

3.1、React 脚手架意义
  1. 脚手架是开发现代 web 应用的必备
  2. 充分利用 webpack、Babel、ESLint 等工具辅助项目开发
  3. 零配置,无需手动配置繁琐的工具即可使用
  4. 关注业务,而不是工具配置
3.2、使用 React 脚手架初始化项目
  1. 初始化项目,命令:npx create-react-app my-app
  2. 启动项目,在项目根目录执行命令:npm start

npx 命令介绍

  • npm v5.2.0 引入的一条命令
  • 目的:提升包内提供的命令行工具的使用体验
  • 原来:先安装脚手架包,再使用这个包中提供的命令
  • 现在:无需安装脚手架包,就可以直接使用这个包提供的命令

补充说明

  1. 推荐使用:npx create-react-app my-app
  2. npm init react-app my-app
  3. yarn create react-app my-app
  • yarn 是 Fecebook 发布的包管理器,可以看做是 npm 的替代品,功能于 npm 相同
  • yarn 具有快速、可靠和安全的特点
  • 初始化新项目:yarn init
  • 安装包:yarn add 包名称
  • 安装项目依赖项:yarn
3.3、在脚手架中使用 React
  1. 导入 react 和 react-dom 两个包
import React from 'react'
import ReactDOM from 'react-dom'
  1. 调用 React.createElement() 方法创建 react 元素
  2. 调用 ReactDOM.render() 方法渲染 react 元素到页面中

4、JSX 的基本使用

4.1、createElement() 的问题
  1. 繁琐不简洁
  2. 不直观,无法一眼看出所描述的结构
  3. 不优雅,用户体验不爽
4.2、JSX 简介
  • JSX 是 JavaScript XML 的简写,表示在 JavaScript 代码中写 XML(HTML)格式的代码
  • JSX 是 React 的核心内容
4.3、使用步骤
  1. 使用 JSX 语法创建 react 元素
# 使用 JSX 语法,创建 react 元素
const title = <h1>Hello JSX</h1>
  1. 使用 ReactDOM.render() 方法渲染 react 元素到页面中
# 渲染创建好的 React 元素
ReactDOM.render(title, root)
4.4、为什么脚手架中可以使用 JSX 语法?
  1. JSX 不是表准的 ECMAScript 语法,它是 ECMAScript 的语法扩展
  2. 需要使用 babel 编译处理后,才能在浏览器环境中使用
  3. create-react-app 脚手架中已经默认有该配置,无序手动配置
  4. 编译 JSX 语法的包为:@babel/preset-react
4.5、使用 JSX 注意点
  1. React 元素的属性名使用驼峰命名法
  2. 特殊属性名:class -> className、for -> htmlFor、tabindex -> tabIndex
  3. 没有子节点的 React 元素可以用 /> 结束
  4. 推荐使用小括号包裹 JSX,从而避免 JS 中的自动插入分号陷阱
4.6、JSX 中使用 JavaScript 表达式
  • 数据存储在 JS 中
  • 语法:{ JavaScript 表达式 }
  • 注意:语法中是单大括号,不是双大括号
const name = 'Jack'
const dv = (
  <div>你好,我叫:{ name }</div>
)

注意

  • 单大括号中可以使用任意的 JavaScript 表达式
  • JSX 自身也是 JS 表达式
  • JS 中的对象是一个例外,一般只会出现在 style 属性中
  • 不能在 {} 中出现语句
4.7、JSX 中的条件渲染
  • 根据条件渲染特定的 JSX 结构
  • 可以使用 if/else 或三元运算符或逻辑运算符来实现
const loadData = () => {
  if(isLoading) {
    return <div>数据加载中,请稍后...</div>
  }
  return (
    <div>书记加载完成,此处显示加载后的数据</div>
  )
}

const dv = (
  <div>{ loadData() }</div>
)
4.7、JSX 的列表渲染
  • 如果要渲染一组数据,应该使用数组的 map() 方法
  • 渲染列表时应该添加 key 属性,key 属性的值要保证唯一
  • map() 遍历谁,就给谁添加 key 属性
  • 尽量避免使用索引号作为 key
const songs = [
  { id: 1, name: '郭靖' },
  { id: 2, name: '黄药师' },
  { id: 3, name: '中神通' },
]

const list = (
  <ul>
    { songs.map(item => (<li key={item.id}>{item.name}</li>))}
  </ul>
)
4.8、JSX 的样式处理
  1. 行内样式 style
<h1 style={{ color: 'red', backgroundColor: 'white' }}>
  JSX 的样式处理
</h1>
  1. 类名 className
<h1 className="title">
  JSX 的样式处理
</h1>

5、React 组件

5.1、React 组件介绍
  • 组件是 React 的一等公民,使用 React 就是在用组件
  • 组件表示页面中的部分内容
  • 组合多个组件实现完整的页面功能
  • 特点:可复用、独立、可组合
5.2、React 组件的两种创建方式
  1. 使用函数创建组件
  • 函数组件:使用 JS 的函数(或箭头函数)创建的组件
  • 函数名称必须以大写字母开头
  • 函数组件必须有返回值,表示该组件的结构
  • 如果返回值为 null,表示不渲染任何内容
# 用函数名作为组件标签名
# 组件标签可以是单标签也可以是双标签
function Hello() {
  return (
    <div>这是我的第一个函数组件!</div>
  )
}
ReactDOM.render(<Hello/>, root);
  1. 使用类创建组件
  • 使用 ES6 的 class 创建的组件
  • 类名称也必须是以大写字母开头
  • 类组件应该继承 React.Component 父类,从而可以使用父类中提供的方法或属性
  • 类组件必须提供 render() 方法
  • render() 方法必须有返回值,表示该组件的结构
class Hello extends React.Component {
  render() {
    return <div>Hello Class Component!</div>
  }
}
ReactDOM.render(<Hello/>, root)
5.3、React 事件处理
  1. 事件绑定
  • React 事件绑定语法与 DOM 事件语法相似
  • 语法:on+事件名称={事件处理程序},比如:onClick={() => {}}
  • React 事件采用驼峰命名法,比如:onMouseEnter、onFocus
# 函数组件绑定事件
function App() {
  function handleClick() {
    console.log('单击事件触发了');
  }
  return (
    <button onClick={handleClick}>点击</button>
  )
}
# 类组件绑定事件
class App extends React.Component {
  handleClick() {
    console.log('单击事件触发了')
  }
  render() {
    return (
      <button onClick={this.handleClick}>点击</button>
    )
  }
}
  1. 事件对象
  • 可以通过事件处理程序的参数获取到事件对象
  • React 中的事件对象叫做:合成事件(对象)
  • 合成事件:兼容所有浏览器,无需担心跨浏览器兼容性问题
function handleClick(e) {
  e.preventDefault()
  console.log('事件对象', e)
}
<a onClick={handleClick}>点我,不会跳转页面</a>
5.4、由状态组件和无状态组件
  • 函数组件又叫做无状态组件,类组件又叫做有状态组件
  • 状态(state)即数据
  • 函数组件没有自己的状态,指负责数据展示(静)
  • 类组件有自己的状态,负责更新 UI,让页面动起来
5.5、组件中的 state 和 setState
  1. state 的基本使用
  • 状态(state)即数据,是组件内部的私有数据,只能在组件内部使用
  • state 的值是对象,表示一个组件中可以有多个数据
# 通过 this.state 来获取状态
class Hello extends React.Component {
  constructor() {
    super()
    <!-- 初始化state -->
    this.state = {
      count: 0
    }
  }
  render() {
    return (
      <div>有状态组件, { this.state.count }</div>
    )
  }
}
# 通过 this.state 来获取状态
class Hello extends React.Component {
  <!-- 简化语法 -->
  state = {
    count: 0
  }
  render() {
    return (
      <div>由状态组件,{ this.state.count }</div>
    )
  }
}
  1. setState()修改状态
  • 状态是可变的
  • 语法:this.setState({ 要修改的数据 })
  • 注意:不要直接修改 state 中的值,这是错误的
  • setState() 作用:修改 state、更新 UI
  • 思想:数据驱动视图
# 正确写法
this.setState({
  count: this.state.count + 1
})

# 错误写法
this.state.count += 1

从 JSX 中抽离事件处理程序

  • JSX 中掺杂过多 JS 逻辑代码,会显得非常混乱
  • 将逻辑抽离到单独的方法中,保证 JSX 结构清晰
5.6、事件绑定 this 指向
  1. 箭头函数
  • 利用箭头函数自身不绑定 this 的特点
  • render() 方法中的 this 为组件实例,可以获取到 setState()
class Hello extends React.Component {
  onIncrement() {
    this.setState({ ... })
  }
  render() {
    <!-- 箭头函数中的this指向外部环境,此处为:render()方法 -->
    return (
      <button onClick={ () => this.onIncrement() }></button>
    )
  }
}
  1. Function.prototype.bind()
  • 利用 ES5 中 bind 方法,将事件处理程序中的 this 与组件实例绑定到一起
class Hello extends React.Component {
  constructor() {
    super()
    this.onIncrement = this.onIncrement.bind(this)
  }
  onIncrement() {
    this.setState({ ... })
  }
  render() {
    return (
      <button onClick={this.onIncrement}></button>
    )
  }
}
  1. class 的实例方法
  • 利用箭头函数形式的 class 实例方法
  • 该语法是实验性语法,但是由于 babel 的存在可以直接使用
class Hello extends React.Component {
  onIncrement = () => {
    this.setState({...})
  }
  render() {
    return (
      <button onClick={this.onIncrement}></button>
    )
  }
}
5.7、表单处理
  1. 受控组件
  • HTML 中的表单元素是可输入的,也就是有自己的可变状态
  • React 中可变状态通常保存在 state 中,并且只能通过 setState() 方法来修改
  • React 将 state 与表单元素值 value 绑定到一起,由 state 的值来控制表单元素的值
  • 受控组件:其值受到 React 控制的表单元素
class Hello extends React.Component {
  state = {
    name: ''
  }
  render() {
    return (
      <input type="text" value="{ this.state.name }" onChange={e => this.setState({ name: e.target.value })}/>
    )
  }
}
  1. 非受控组件(DOM 方式)
  • 借助于 ref,使用原生 DOM 方式来获取表单元素值
  • ref 的作用:获取 DOM 或组件
# 调用 React.createRef() 方法创建一个 ref 对象
constructor() {
  super()
  this.txtRef = React.createRef()
}

# 将创建好的 ref 对象添加到文本框中
<input type="text" ref="{ this.txtRef }"/>

# 通过 ref 对象获取到文本框的值
console.log(this.txtRef.current.value)

6、React 组件进阶

6.1、组件通讯介绍

组件时独立且封闭的单元,默认情况下,只能使用组件自己的数据。在组件化过程中,我们将一个完整的功能拆分成多个组件,以更好的完成整个应用的功能。而在设个过程中,多个组件之间不可避免地要共享某些数据。为了实现这些功能,就需要打破组件地独立性,让其与外界沟通。这个过程就是组件通讯

6.2、组件的 props
  • 组件是封闭的,要接收外部数据应该通过 props 来实现
  • props 的作用:接收传递给组件的数据
  • 传递数据:给组件标签添加属性
  • 接收数据:函数组件通过参数 props 接收数据,类组件通过 this.props 接收数据
# 函数组件
function Hello(props) {
  return (
    <div>接收到数据:{ props.name }</div>
  )
}

<Hello name="jack" age={19}/>
# 类组件
class Hello extends React.Component {
  render() {
    return (
      <div>接收到的数据:{ this.props.age }</div>
    )
  }
}
<Hello name="jack" age={19}/>

特点

  1. 可以给组件传递任意类型的数据
  2. props 是只读的对象,只能读取属性的值,无法修改对象
  3. 使用类组件时,如果写了构造函数,应该将 props 传递给 super(),否则无法在构造函数中获取到 props
class Hello extends React.Component {
  constructor(props) {
    <!-- 推荐将 props 传递给父类构造函数 -->
    super(props)
  }
  render() {
    return (
      <div>接收的数据:{this.props.age}</div>
    )
  }
}
6.3、组件通讯的三种方式
6.3.1 父组件传递数据给子组件
  1. 父组件提供要传递的 state 数据
  2. 给子组件标签添加属性,值为 state 中的数据
  3. 子组件中通过 props 接收父组件中传递的数据
# 父组件
class Parent extends React.Component {
  state = { lastName: '王' }
  render() {
    return (
      <div>传递数据给子组件:<Child name={this.state.lastName}/></div>
    )
  }
}
# 子组件
function Child(props) {
  return <div>子组件接收到数据:{props.name}</div>
}
6.3.2 子组件传递数据给父组件
  • 利用回调函数,父组件提供回调,子组件调用,将要传递的数据作为回调函数的参数
  • 父组件提供一个回调函数(用于接收数据)
  • 将该函数作为属性的值,传递给子组件
# 父组件
class Parent extends React.Component {
  getChildMsg = (msg) => {
    console.log('接收到子组件数据', msg)
  }
  render() {
    return (
      <div>子组件:<Child getMsg={this.getChildMsg}/></div>
    )
  }
}
# 子组件
class Child extends React.Component {
  state = { childMsg: 'React'}
  handleClick = () => {
    this.props.getMsg(this.state.childMsg)
  }
  render() {
    return (
      <button onClick={this.handleClick}>点击,给父组件传递数据</button>
    )
  }
}
6.3.3 兄弟组件
  • 将共享状态提升到最近的公共父组件中,由公共父组件管理这个状态
  • 思想:状态提升
  • 公共父组件职责:提供共享状态、提供操作共享状态的方法
  • 要通讯的子组件只需通过 props 接收状态或操作状态的方法
6.4、Context
  • 如果两个组件是远房亲戚(比如,嵌套多层)可以使用 Context 实现组件通讯
  • Context 提供了两个组件:Provider 和 Consumer
  • Provider 组件:用来提供数据
  • Consumer 组件:用来消费数据
# 调用 React.createContext() 创建 Provider(提供数据)和 Consumer(消费数据)两个组件
const { Provider, Consumer } = React.createContext()

# 使用 Provider 组件作为父节点
<Provider>
  <div className="App">
    <Child/>
  </div>
</Provider>

# 设置 value 属性,表示要传递的数据
<Provider value="pink"/>

# 调用 Consumer 组件接收数据
<Consumer>
  {data => <span>data参数表示接收到的数据 -- {data}</span>}
</Consumer>
6.5、props 深入
6.5.1 chilren 属性
  • children 属性:表示组件标签的子节点。当组件标签有子节点时,props 就会有该属性
  • children 属性与普通的 props 一样,值可以是任意值(文本、React 元素、组件、甚至是函数)
function Hello(props) {
  return (
    <div>组件的子节点:{props.children}</div>
  )
}
<Hello>我是子节点</Hello>
6.5.2 props 校验
  • 对于组件来说,props 是外来的,无法保存组件使用者传入什么格式的数据
  • 如果传入的数据格式不对,可能会导致组件内部报错
  • props 校验:允许在创建组件的时候,就指定 props 的类型、格式等
  • 捕获使用组件时因为 props 导致的错误,给出明确的错误提示,增加组件的健壮性

使用步骤

  1. 安装包 prop-types (yarn add prop-types / npm i props-type)
  2. 导入 prop-types 包
  3. 使用组件名.propTypes = {} 来给组件的 props 添加检验规则
  4. 检验规则通过 PropTypes 对象来指定
import PropTypes form 'prop-types'
function App(props) {
  return (
    <h1>Hi,{props.colors}</h1>
  )
}
App.propTypes = {
  # 约定 colors 属性为 array类型
  # 如果类型不对,则报出明确错误,便于分析错误原因
  colors: PropTypes.array
}

约束规则

  1. 常见类型:array、bool、func、number、object、string
  2. React 元素类型:element
  3. 必填项:isRequired
  4. 特定结构的对象:shape({})
# 常见类型
optionalFunc: PropTypes.func

# 必选
requiredFunc: PropTypes.func.isRequired

# 特定结构的对象
optionalObjectWithShape: PropTypes.shape({
  color: PropTypes.string,
  fontSize: PropTypes.number
})
6.5.3 props 的默认值
  • 场景:分页组件 -> 每页显示条数
  • 作用:给 props 设置默认值,在末传入 props 时生效
function App(props) {
  return (
    <div>此处展示props的默认值:{props.pageSize}</div>
  )
}

# 设置默认值
App.defaultProps = {
  pageSize: 10
}

7、组件的生命周期

7.1、组件的生命周期概述
  • 组件的生命周期有助于理解组件的运行方式、完成更复杂的组件功能、分析组件错误原因等
  • 组件的生命周期:组件从被创建到挂载到页面中运行,再到组件不用时卸载的过程
  • 生命周期的每个阶段总是伴随着一些方法调用,这些方法就是生命周期的钩子函数
  • 钩子函数的作用:开发人员在不同阶段操作组件提供了时机
  • 只有类组件才有生命周期
7.2、生命周期的三个阶段
  1. 创建时(挂载阶段)
  • 执行时机:组件创建时(页面加载时)
  • 执行顺序:constructor() -> render() -> componentDidMount
钩子函数触发时机作用
constructor创建组件时,最先执行初始化 state、为事件处理程序绑定 this
render每次组件渲染都会触发渲染 UI(注意:不能调用 setState)
componentDidMount组件挂载(完成 DOM 渲染)后发送网络请求、DOM 操作
  1. 更新时(更新阶段)
  • 执行时机:setState()、forceUpdate()、组件接收到新的 props
  • 以上三者任意一种变化,组件就会从新渲染
  • 执行顺序:render() -> componentDidUpdate()
钩子函数触发时机作用
render每次组件渲染都会触发渲染 UI(与挂载阶段是同一个 render)
componentDidUpdate组件更新(完成 DOM 渲染)后发起网络请求、DOM 操作
  1. 卸载时(卸载阶段)
  • 执行时机:组件从页面中消失
钩子函数触发时机作用
componentWillUnmount组件卸载(从页面中消失)执行清理工作(清理定时器)

8、render-props 和高阶组件

8.1、React 组件复用概述
  • 思考:如果两个组件中的部分功能相似或相同,该如何处理
  • 处理方式:复用相似的功能
  • 复用什么?1. state 2. 操作 state 的方法
  • 两种方式:render props 模式 2. 高阶组件(HOC)
  • 这两种方式不是新的 API,而是利用 React 自身特点的编码技巧,演化而成的固定模式
8.2、render props 模式
  • 创建 Mouse 组件,在组件中提供复用的状态逻辑代码(状态、操作状态的方法)
  • 将要复用的状态作为 props.render(state)方法的参数,暴露到组件外部
  • 使用 props.render() 的返回值作为要渲染的内容
  • 不一定要叫 render 也可以使用其他名称,复用时与之一致即可
# 定义一个 Mouse 组件
export default class Mouse extends React.Component {
  state = {
    x: 0,
    y: 0
  }
  <!-- 监听鼠标移动事件 -->
  componentDidMount() {
    window.addEventListener('mousemove', this.handleMouseMove)
  }
  <!-- 组件卸载时解绑事件 -->
  componentWillUnmount() {
    window.removeEventListener('mousemove', this.handleMouseMove)
  }
  handleMouseMove = (e) => {
    this.setState({
      x: e.clientX,
      y: e.clientY
    })
  }
  render() {
    <!-- 向外界提供当前子组件里面的数据 render 模式 -->
    <!-- children 是自定义的名称,复用时与之对应即可 -->
    return this.props.children(this.state)
  }
}
# 定义一个 Position 类式组件
export default class Position extends React.Component {
  render() {
    <div>
      <h1>鼠标当前x坐标轴:{this.props.x}</h1>
      <h2>鼠标当前y坐标轴:{this.props.y}</h2>
    </div>
  }
}
# 在 App 组件中使用
export default class App extends React.Component {
  render() {
    return (
      <div>
        <Mouse>
          { mouse => <Position {...mouse}></Position> }
        </Mouse>
      </div>
    )
  }
}
8.3、高阶组件
  • 高阶组件(HOC, Higher-Order Component)是一个函数,接收要包装的组件,返回增强后的组件
  • 高阶组件内部创建一个类组件,这个类组件中提供复用的状态逻辑代码,通过 props 将复用的状态传递给包装组件

使用步骤

  1. 创建一个函数,名称约定以 with 开头
  2. 指定函数参数,参数应该以大写字母开头,作为要渲染的组件
  3. 在函数内部创建一个类组件,提供复用的状态逻辑代码,并返回
  4. 在该组件中,渲染参数组件,同时将状态通过 prop 传递给参数组件
  5. 调用该高阶组件,传入要增强的组件,通过返回值拿到增强后的组件,并将其渲染到页面中
# 定义一个函数,在函数内部创建一个类式组件
# 传递的参数是一个组件,所以形参的首字母需要大写
const withMouse = (warppedComponent) => {
  class Mouse extends Component {
    state = {
      x: 0,
      y: 0
    }
    <!-- 监听鼠标移动事件 -->
    componentDidMount() {
      window.addEventListener('mousemove', this.handleMouseMove)
    }
    <!-- 组件限载时解绑事件 -->
    componentWillUnmount() {
      window.removeEventListener('mousemove', this.handleMouseMove)
    }
    <!-- 事件的处理函数 -->
    handleMouseMove = (e) => {
      this.setState({
        x: e.clientX,
        y: e.clientY
      })
    }
    render() {
      <!-- 返回传递过来的组件,把当前组件的状态设置进去 -->
      return <warppedComponent {...this.state}/>
    }
  }
  <!-- 设置开发者工具显示名称,如果不设置开发者工具显示的外壳组件都是一样的 -->
  function getDisplayName(warppedComponent) {
    return warppedComponent.displayName || warppedComponent.name || 'Component'
  }
  <!-- 给组件添加displayName属性 -->
  Mouse.displayName = `withMouse${getDisplayName(warppedComponent)}`
  return Mouse
}
export default withMouse
# 在组件内使用高阶组件
# 引入高阶组件,将需要增强的组件传入高阶组件

class Position extends React.Component {
  render() {
    return (
      <div>
        <h2>鼠标当前x坐标轴:{this.props.x}</h2>
        <h2>鼠标当前y坐标轴:{this.props.y}</h2>
      </div>
    )
  }
}
export default withMouse(Position)
# 在 App 组件内使用的方法和一般情况一致
export default class App extends React.Component {
  render() {
    return (
      <Position/>
    )
  }
}

9、React 原理揭秘

9.1、setState() 的说明
  1. 更新数据
  • setState() 是异步更新数据的
  • 使用该语法时,后面的 setState() 不依赖前面的 setState()
  • 可以调用多次 setState(), 只会触发一次重新渲染
this.state = { count: 1 }
this.setState({
  count: this.state.count + 1
})
console.log(this.state.count) // 1
  1. 推荐语法
  • 使用 setState((state, props) => {}) 语法
  • 参数 state: 表示最新的 state
  • 参数 props: 表示最新的 props
this.setState((state, props) => {
  return {
    count: state.count + 1
  }
})
console.log(this.state.count) // 1
  1. 第二个参数
  • 在状态更新(页面完成重新渲染)后立即执行某个操作
  • 语法:setState(update[,callback])
this.setState((state, props) => {}, () => {console.log('这个回调函数会在状态更新后立即执行')})
9.2、JSX 语法的转化过程
  • JSX 仅仅是 createElement() 方法的语法糖(简化语法)
  • JSX 语法被 @babel/preset-react 插件编译为 createElement() 方法
  • React 元素:是一个对象,用来描述你希望在屏幕上看到的内容
# JSX 语法
const element = (
  <h1 className="greeting">
    Hello JSX!
  </h1>
)
# createElement()
const element =
React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello JSX!'
)
React 元素
const element = {
  type: 'h1',
  props: {
    className: 'greeting',
    children: 'Hello JSX!'
  }
}
9.3、组件更新机制
  • setState() 的两个作用:1.修改 state 2.更新组件(UI)
  • 父组件重新渲染时,也会重新渲染子组件。但只会渲染当前组件子树(当前组件及其所有子组件)
9.4、组件性能优化
  1. 减轻 state
  • 减轻 state,只存储跟组件渲染相关的数据
  • 不要做渲染的数据不要放在 state 中,比如定时器 id 等
  • 对于这种需要在多个方法中用到的数据,应该放在 this 中
  1. 避免不必要的重新渲染
  • 组件更新机制:父组件更新会引起子组件也被更新,这种思路很清晰
  • 问题:子组件没有任何变化时也会重新渲染
  • 如何避免不必要的重新渲染
  • 解决方式:使用钩子函数 shouldComponentUpdate(nextProps, nextState)
  • 作用:通过返回值决定改组件是否重新渲染,返回 true 表示重新渲染,false 表示不重新渲染
  • 触发时机:更新阶段的钩子函数,组件重新渲染前执行(shouldComponentUpdate -> render)
# 随机数
class Hello extends React.Component {
  shouldComponentUpdate(nextProps, nextState) {
    return nextState.number !== this.state.number
  }
}
  1. 纯组件
  • 纯组件:PureComponent 与 React.Component 功能相似
  • 区别:PureComponent 内部自动实现了 shouldComponentUpdate 钩子,不需要手动比较
  • 原理:纯组件内部通过分别对比前后两次 props 和 state 的值,来决定是否重新渲染组件
class Hello extends React.PureComponent {
  render() {
    return (
      <div>纯组件</div>
    )
  }
}
9.5、虚拟 DOM 和 Diff 算法
  • 虚拟 DOM 本质上就是一个 JS 对象,用来描述你希望在屏幕上看到的内容
  • 初次渲染时,React 会根据初始 state(Model),创建一个虚拟 DOM 对象
  • 根据虚拟 DOM 生成真正的 DOM,渲染到页面中
  • 当数据变化后(setState()),重新根据新的数据,创建新的虚拟 DOM 对象
  • 与上次得到的虚拟 DOM 对象,使用 Diff 算法对比,的到需要更新的内容
  • 最终,React 只将变化的内容更新(patch)到 DOM 中,重新渲染到页面

10、React 路由基础

10.1、React 路由介绍

现代的前端应用大多都是 SPA(单页面应用程序),也就是只有一个 HTML 页面的应用程序。因为它的用户体验更好、对服务器的压力更小、对服务器的压力更小,所以更受欢迎。为了有效的使用单个页面来管理原来多页面的功能。前端路由应运而生。

  • 前端路由的功能:让用户从一个视图(页面)导航到另一个视图(页面)
  • 前端路由是一套映射规则,在 React 中,是 URL 路径与组件的对应关系
  • 使用 React 路由简单来说,就是配置路径和组件
10.2、路由的基本使用

使用步骤

  1. 安装:yarn add react-router-dom
  2. 导入路由的三个核心组件:Router/Route/Link
import { BrowserRouter as Router, Route, Link } from 'react-router-dom'
  1. 使用 Router 组件包裹整个应用
<Router>
  <div class="App"></div>
</Router>
  1. 使用 Link 组件作为导航菜单
<Link to="/first">页面一</Link>
  1. 使用 Route 组件配置路由规则和要展示的组件
const First = () => (<p>页面一的页面内容</p>)
<Router>
  <div className="App">
    <Link to="/first">页面一</Link>
    <Route path="/first" component={First}></Route>
  </div>
</Router>

常用组件说明

  • Router 组件:包裹整个应用,一个 React 应用只需要使用一次
  • 两种常用 Router:HashRouter 和 BrowserRouter
  • HashRouter: 使用 URL 的哈希值实现(http://loaclhost:3000/#/first)
  • BrowserRouter:使用 H5 的 history API 实现(http://loaclhost:3000/first)
  • Link 组件:用于指定导航链接(a 标签)
# to 属性:浏览器地址栏中的pathname(location.pathname)
<Link to="/first">页面一</Link>
  • Route 组件:指定路由展示组件相关信息
# path属性:路由规则
# component 属性:展示的组件
# Route 组件写在哪,渲染出来的组件就展示在哪
<Route path="/first" component={First}></Route>
10.3、路由的执行过程
  1. 点击 Link 组件(a 标签),修改了额浏览器地址栏中的 url
  2. React 路由监听到地址栏 url 的变化
  3. React 路由内部遍历所有 Route 组件,使用路由规则(path)与 pathname 进行匹配
  4. 当路由规则(path)能够匹配地址栏中的 pathname 时,就展示该 Route 组件的内容
10.4、编程式导航
  • 场景:点击登录按钮,登录成功后,通过代码跳转到后台首页,如何实现
  • 编程式导航:通过 JS 代码来实现页面跳转
  • history 是 React 路由提供的,用于获取浏览器历史记录的相关信息
  • push(path):跳转到某个页面,参数 path 表示要跳转的路径
  • go(n): 前进或后退到某个页面,参数 n 表示前进或后退页面的数量
class Login extends React.Component {
  handleLogin = () => {
    this.props.history.push('/home')
  }
  render() {...}
}
10.5、默认理由
  • 问题:现在的路由都是点击导航菜单后展示的,如何在进入页面的时候就展示
  • 默认路由:表示进入页面时就会匹配的路由
  • 默认路由 path 为:/
<Route path="/" component={Home}/>
10.6、匹配模式
  1. 模糊匹配模式
  • 问题:当 Link 组件的 to 属性值为/login时为什么默认路由也被匹配成功?
  • 默认情况下,React 路由是模糊匹配模式
  • 模糊匹配规则:只要 pathname 以 path 开头就会匹配成功
<Link to="/login">登录页面</Link>
<Route path="/" component={Home}></Route> // 匹配成功
# path 代表 Route 组件的 path 属性
# pathname 代表Link组件的to属性(也就是 location.pathname)
path能够匹配的 pathname
/所有 pathname
/first/first 或/first/a 或/first/a/b/…
  1. 精确匹配
  • 问题:默认路由任何情况下都会展示,如何避免这种问题?
  • 给 Route 组件添加 exact 属性,让其变为精确匹配模式
  • 精确匹配:只有当 path 和 pathname 完全匹配时才会展示该路由
# 此时,该组件只能匹配 pathname="/"这种情况
<Route exact path="/" component=...></Route>
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

alonghuang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值