React


# React

# 什么是 JSX

JSX

  • jsx 是 javscript + xml, 是一种 js 的扩展语法
  • jsx 是一种语法糖,最终会经过 babel 的转义
  • <h1>hello world</h1> 会被转义为 React.createElement("h1", null, 'hello world')
  • jsx 也是对象, 可以作为方法的参数 和返回值

# 什么是 react 元素

React 元素

  • React 元素其实就是普通的 js 对象, 它里边有很多的属性, 描述元素的样子
  • ReactDOM.render 可以保证浏览器界面的显示 和 React 元素保持一致
  • 原生 dom 元素被 React.createElement() 处理成对象的形式
// 比如这样的标签
;<h1>Helllo</h1>
// 会被createElement()方法包装成对象
let reactObj = React.createElement('h1', null, 'Hello')
// 打印会得到下边的信息
console.log(reactObj)
1
2
3
4
5
6

# 如何更新 Dom 元素

  • react 元素本身是不可变的, 当 react 元素被创建之后,更新界面的唯一方式就是创建一个新的元素,然后重新渲染
  • React 只会更新必要的部分, 内部通过 dom-diff 算法进行比较

# state 状态

状态

  • 数据的来源有两个地方, 分别是属性 对象 和 状态对象
  • 属性是父组件传递过来的
  • 状态是自己内部的,改变状态唯一的方式就是 setState
  • 属性和状态的变化都会影响视图的更新

# state 状态的改变

  • 不能直接修改 state 的值
  • 只能使用 setState 更新状态机
import React from 'react'
import ReactDom from 'react-dom'
interface Props {}
interface State {
  num: number
}
// 类组件有两个泛型, 一个 约束props, 一个约束state
class Clock extends React.Component<Props, State> {
  state = {
    num: 0
  }
  handleClick = (e: React.MouseEvent) => {
    // 改变原有的值
    this.setState({ num: this.state.num + 1 })
  }
  render() {
    return (
      <div>
        <p> {this.state.num}</p>
        <button onClick={this.handleClick}>+</button>
      </div>
    )
  }
}
ReactDom.render(<Clock />, document.getElementById('root')!)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# state 改变的同步和异步

  • 异步更新,即使数据改变了也不会立马更新视图
interface Props {}
interface State {
  num: number
}
// 组件有两个泛型, 一个 约束props, 一个约束state
class Clock extends React.Component<Props, State> {
  state = {
    num: 0
  }
  // setState 的更新可能是异步的, 多个setState 可能会被合并成一个,如果只改变一部分状态,会被合并到总的状态上边
  // react 对事件进行约束
  handleClick = (e: React.MouseEvent) => {
    // 这样连续掉3次 会进行异步合并, 并不会立马更新,而是先缓存起来
    this.setState({ num: this.state.num + 1 })
    this.setState({ num: this.state.num + 1 })
    this.setState({ num: this.state.num + 1 })
  }
  render() {
    return (
      <div>
        <p> {this.state.num}</p>
        <button onClick={this.handleClick}>+</button>
      </div>
    )
  }
}

ReactDom.render(<Clock />, document.getElementById('root')!)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
  • 如果想再值改变之后立马触发视图更新,可以使用函数的方式
interface Props {}
interface State {
  num: number
}
// 组件有两个泛型, 一个 约束props, 一个约束state
class Clock extends React.Component<Props, State> {
  state = {
    num: 0
  }
  // setState 的更新可能是异步的, 多个setState 可能会被合并成一个
  // react 对事件进行约束
  handleClick = (e: React.MouseEvent) => {
    // 这样连续掉3次 会立马更新
    this.setState(state => ({ num: state.num + 2 })
    this.setState(state => ({ num: state.num + 2 })
    this.setState(state => ({ num: state.num + 2 })
  }
  render() {
    return (
      <div>
        <p> {this.state.num}</p>
        <button onClick={this.handleClick}>+</button>
      </div>
    )
  }
}

ReactDom.render(<Clock />, document.getElementById('root')!)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
  • setTimeout 里边的代码比较特殊, 不会走批量更新, 会立刻更新
handleClick = (e: React.MouseEvent) => {
  this.setState({ num: this.state.num + 1 })
  console.log(this.state.num) // 0
  this.setState({ num: this.state.num + 1 })
  console.log(this.state.num) // 0
  setTimeout(() => {
    this.setState({ num: this.state.num + 1 })
    console.log(this.state.num) // 2
  }, 0)
}
1
2
3
4
5
6
7
8
9
10

# 组件创建

组件分两种: 函数式组件类组件

函数式组件就是一个函数, 接收一个属性对象 并返回 react 元素

// 定义一个组件, props的类型是Props 接口定义的类型, 返回 React 元素
function Welecom(props: Props): React.ReactElement {
  return <h1>hello {props.name}</h1>
}
1
2
3
4
  • 函数式组价是如何渲染的?
      1. 收集 props 对象 => (props = {name:"王老吉"})
      1. 把属性对象传入函数,并返回 React 元素 => (Welecom(props: Props))
      1. 把 React 元素渲染到页面上 => ReactDOM.render

类组件 使用 class 来定义 并继承自 React.Component

// 类组件, 需要 用泛型来约束 传入的属性
class App extends React.Component<Props> {
  // 约束render 函数, 返回的是react 元素
  render(): React.ReactElement {
    return <h1>hello, {this.props.name}</h1>
  }
}
1
2
3
4
5
6
7
  • 类组件是如何渲染的?
      1. 收集 props 对象
      1. 实例化类组件的实例
      1. 调用类组件的 render 方法, 获得返回的 react 元素
      1. 把返回的 react 元素 渲染到界面上

注意事项

  • React 元素可以当初 Dom 标签, 也可以是用户自定义的组件
  • Dom 标签首字母与小写的, 自定义组件首字母大写
  • 组件要先定义再使用
  • 函数组件 只有 return 一个 React 元素, 类组件需要提供 render 方法,来返回元素

# 组件传值

React 组件传值,分为 3 种基本方式

  • 父向子组件传值, 父组件直接在子组件的标签上声明要传入的属性,子组件通过 props 参数接收,并且 props 是只读的单一数据流
// 父组件中使用了子组件Child
<Child name='hello' id={this.state.id} changeNum={this.handle} />
1
2
// 子组件中通过props接收属性
const Child = (props: Props) => {
  // 子组件改变父组件的值
  const handleClick = () => props.changeNum(50)
  return (
    <div>
      <p>child组件</p>
      <p>{props.name}</p>
      <p>{props.id}</p>
      <button onClick={handleClick}>子的点击事件</button>
    </div>
  )
}
1
2
3
4
5
6
7
8
9
10
11
12
13
  • 子组件向父组件传值, 就需要在父组件提前声明一个方法传给子组件
// 父组件声明一个方法
<Child changeNum={this.handle} />
1
2
// 子组件通过父组件传入的方法改变父组件的值
const Child = (props: Props) => {
  const handleClick = () => props.changeNum(50)
  return (
    <div>
      <p>child组件</p>
      <button onClick={handleClick}>子的点击事件</button>
    </div>
  )
}
1
2
3
4
5
6
7
8
9
10
  • 兄弟组件传值(该方法同样适用于父子组件),使用 context
//1. 创建一个全局上下文
const ThemeContext = React.createContext()
// 2. 创建上下文组件容器
<ThemeContext.Provider value='china is the very good contry'></ThemeContext.Provider>
// 3. 获取上下文容器中的 value 值,在组件中使用
<ThemeContext.Consumer>
    {value => {
        return (
        <div>
            {value}
        </div>
        )
    }}
</ThemeContext.Consumer>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 父向子传值

父组件直接设置属性和值,子组件通过 props 接收

父组件

import * as React from 'react'
import Child from './Child'
class App extends React.Component {
  state = {
    id: 10
  }
  render() {
    return (
      <div>
        {/* 传入3个属性 name, id */}
        <Child name='hello' id={this.state.id} />
      </div>
    )
  }
}
export default App
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

子组件接收

import * as React from 'react'
// 定义一个接口,约束父组件传入的属性
interface Props {
  name: string
  id: number
}
// Props接口 约束 props 参数
const Child = (props: Props) => {
  return (
    <div>
      <p>child组件</p>
      <p>{props.name}</p>
      <p>{props.id}</p>
    </div>
  )
}
export default Child
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 子向父传(或改变父的)值

父组件 要提前声明一个方法传给子组件, 子组件会返回一个值作为这个函数的参数

import * as React from 'react'
import Child from './Child'
class App extends React.Component {
  state = {
    id: 10
  }
  handle = (num: number) => {
    this.setState({ id: num })
  }
  render() {
    return (
      <div>
        {/* 传入3个属性 name, id, changeNum */}
        <p>父组件渲染:{this.state.id}</p>
        <Child changeNum={this.handle} />
      </div>
    )
  }
}
export default App
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

子组件

import * as React from 'react'
// 定义一个接口,约束父组件传入的属性
interface Props {
  changeNum: (num: number) => void
}
// Props接口 约束 props 参数
const Child = (props: Props) => {
  // 子组件改变父组件的值
  const handleClick = () => props.changeNum(50)
  // 渲染
  return (
    <div>
      <p>child组件</p>
      <button onClick={handleClick}>子的点击事件</button>
    </div>
  )
}
export default Child
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# Context

Context

Context 适用于 共享那些对于一个组件树而言是“全局”的数据

常用 Api

  • React.createContext(): 创建一个 Context 对象
// 只有当组件所处的树中没有匹配到 Provider 时,其 defaultValue 参数才会生效
const MyContext = React.createContext(defaultValue)
1
2
  • Context.Provider : 作为包裹子组件的容器, 是一个高阶组件, 提供一个 value 属性,传递给组件消费
// 只有当组件所处的树中没有匹配到 Provider 时,其 defaultValue 参数才会生效
  <MyContext.Provider value={/* 某个值 */}>
1
2
  • Context.Consumer : 用在 Provider 高阶组件下 消费的组件内部, 可以订阅到 Provider 上 value 的变更
// 高阶组件, 提供一个回调函数, 必须是同一个Context 对象
<MyContext.Consumer>
  {value => {
    ;<div>{value}</div>
  }}
</MyContext.Consumer>
1
2
3
4
5
6
  • Class.contextType : 用组件名绑定上下文组件, 内部可以直接使用 this.context 获取到Provider上的值
const ThemeContext = React.createContext()
class About extends React.Component {
  render() {
    return <div>About:{this.context}</div>
  }
}
About.contextType = ThemeContext
1
2
3
4
5
6
7

使用 Class.contextType, Context.Provider 演示

import React from 'react'
// 创建一个上下文组件对象
const ThemeContext = React.createContext()

//  创建一个 About 组件
class About extends React.Component {
  render() {
    //   使用 this.context 获取值
    return <div>About:{this.context}</div>
  }
}
// 使用 Class.contextType绑定上下文对象
About.contextType = ThemeContext

// 组件容器
class Context extends React.Component {
  render() {
    return (
      <>
        {/* ThemeContext.Provider 创建供消费的容器 */}
        <ThemeContext.Provider value='dhello world ark'>
          <About />
        </ThemeContext.Provider>
      </>
    )
  }
}
export default Context
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

使用 Context.Consumer, Context.Provider 演示

import React from 'react'
// 创建一个上下文组件对象
const ThemeContext = React.createContext()
//创建Home 组件
class Home extends React.Component {
  render() {
    return (
      <div>
        {/* 使用ThemeContext.Consumer 获取 value的值  */}
        <ThemeContext.Consumer>
          {value => {
            return <div>Home {value}</div>
          }}
        </ThemeContext.Consumer>
      </div>
    )
  }
}
// ==========不需要使用 Class.contextType绑定上下文对象================//

// 组件容器
class Context extends React.Component {
  render() {
    return (
      <>
        {/* ThemeContext.Provider 创建供消费的容器 */}
        <ThemeContext.Provider value='dhello world ark'>
          <Home />
        </ThemeContext.Provider>
      </>
    )
  }
}
export default Context
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

# 组件声明周期

挂载阶段

  • constructor(): 初始化数据操作
  • componentWillMount() : 组件挂载前, 不能拿到真实 Dom
  • render() 页面开始渲染
  • componentDidMount(): Dom 挂载完成, 可以通过 ref 获取到真实 Dom

更新阶段

  • shouldComponentUpdate(nextProps, nextState) : 组件是否需要更新, 仅作为性能优化的方式而存在,返回一个布尔值
  • UNSAFE_componentWillUpdate(nextProps, nextState): 组件更新之前
  • render(): 更新后重新渲染更新的部分
  • componentDidUpdate(prevProps, prevState, snapshot): 会在更新后会被立即调用。首次渲染不会执行此方法。

卸载

  • componentWillUnmount() : 会在组件卸载及销毁之前直接调用,可以再这里清除一些副作用

# 父子组件执行顺序

挂载阶段

  • 初始化及挂载

更新阶段

# Hooks 使用

Hook 是一个特殊的函数,它可以让你“钩入” React 的特性, 让你能在不编写 class 组件的下, 随便使用函数式进行编程, 和声明周期

  • 不要在循环,条件或嵌套函数中调用 Hook, 确保总是在你的 React 函数的最顶层调用他们
  • 只在 React函数组件中使用 Hook, 不要在普通的 JavaScript 函数中调用 Hook

# useState 使用

  • useState() 是一个方法, 唯一的参数就是初始的 state 数据
  • useState() 当前 state 以及更新 state 的函数
// 结构出useState
import React, { useState } from 'react'

// 函数式组件Demo
const Demo = () => {
  // es6 解构 数组
  const [name, setName] = useState('老王')
  const changeName = () => {
    console.log(name) // 老王, 这个值一直被保存, (闭包)
    setName(Math.random(0, 1) * 100) // 改变值利用解构的第二个
  }
  return (
    <div>
      <p>{name}</p>
      <button onClick={changeName}>字符变随机数</button>
    </div>
  )
}

export default Demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# useEffect 使用

useEffect 几乎包含了类组件中的所有生命周期钩子函数的功能, 可以让你在函数组件中执行副作用操作

    1. 如果只传第一个参数,并且第一次初始化:就是 componentDidMount 钩子的功能
    1. 如果只传第一个参数,并且页面数据变化:就是 componentDidUpdate 钩子的功能
    1. 如果第一个参数中最后 return 一个函数,那这个 retrun 的函数,会在切换路由或组件销毁前执行,这个 return 的函数里的执行就是componentWillUnmount 钩子的功能
    1. 如果第二个参数传入一个空数组: 就单纯只是 componentDidMount 钩子的功能,每次数据变化的时候不会在传入的第一个函数类型参数里的数据
    1. 如果第二个参数传入的数组里传入依赖的变化的项: 就是 componentDidMount 和 componentDidUpdate 两个钩子的功能
    1. 对于需要清除的依赖项,需要返回一个函数进行清除副作用,比如定时器,否则会有意想不到的 bug
    1. 再子组件中使用时,如果不传参数, 父组件的值变化了, 就有 componentWillReceiveProps 钩子的功能,子组件会一直接收到变化
    1. 如果传入指定的属性,就是 shoudComponentUpdate 钩子的功能,它就只在这个传入的属性值变化的时候才会执行(可以很明显的知道是哪个属性引起的子组件的变化),可以提高性能

# 👉 useEffect(传入第一个参数)

  • 如果只传第一个参数,并且第一次初始化:就是 componentDidMount 钩子的功能,
  • 当数据变化时, 就是 componentDidUpdate 钩子的功能

也就是说,当页面加载完和 更新完,这个 hook 都会执行,下边代码会走两次

import React, { useState, useEffect } from 'react'

const Demo = () => {
  const [name, setName] = useState('老王')
  const changeName = () => {
    setName(Math.random(0, 1) * 100)
  }
  useEffect(() => {
    console.log('执行useEffect')
  })
  return (
    <div>
      <p>{name}</p>
      <button onClick={changeName}>字符变随机数</button>
    </div>
  )
}

export default Demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 👉 useEffect(return 一个函数)

retrun 这个的函数中,会在切换路由或组件销毁前执行, 这个 return 的函数里的执行就是componentWillUnmount 钩子的功能

比如清除定时器,演示

import React, { useState, useEffect } from 'react'

const Demo = () => {
  const [num, setNum] = useState(1)

  useEffect(() => {
    let timer = setInterval(() => {
      setNum(num + 1)
    }, 1000)
    //   return 一个函数
    return () => {
      console.log('清除定时器')
      clearInterval(timer)
    }
  })
  return (
    <div>
      <p>{num}</p>
    </div>
  )
}

export default Demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 👉 useEffect(componentDidMount 的功能)

  • 第二个参数传入一个空数组: 就单纯只是 componentDidMount 钩子的功能
import React, { useState, useEffect } from 'react'

const Demo = () => {
  const [name, setName] = useState('老王')
  const changeName = () => {
    setName(Math.random(0, 1) * 100)
  }
  // 可以看到数据更新了,也只执行了一次
  useEffect(() => {
    console.log('更新')
  }, [name])

  return (
    <div>
      <p>{name}</p>
      <button onClick={changeName}>字符变随机数</button>
    </div>
  )
}

export default Demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 👉 useEffect(实现依赖某个的值的变化,才会执行)

  • 意思就是说,当页面的某个值变化了,才再次执行,这个函数, 只需要在第二个空数组里传入依赖的值就可以了
import React, { useState, useEffect } from 'react'
const Demo = () => {
  const [name, setName] = useState('老王')
  const [name1, setName1] = useState('hello')
  const changeName = () => {
    setName(Math.random(0, 1) * 100)
  }
  const changeName1 = () => {
    setName1(Math.random(0, 1) * 100)
  }
  // 传入依赖变化的项
  useEffect(() => {
    console.log('name更新')
  }, [name])

  // 什么都不传传入
  useEffect(() => {
    console.log('name1更新')
  }, [])

  return (
    <div>
      <p>{name}</p>
      <p>{name1}</p>
      <button onClick={changeName}>name更新</button>
      <button onClick={changeName1}>name1更新</button>
    </div>
  )
}

export default Demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
  • 可以看到 name1 的值变化多次, 而对应的 useEffect 执行了一次

# 👉 useEffect(componentWillReceiveProps)

import React, { useState, useEffect } from 'react'
const Child = props => {
  // 传入依赖变化的响应的 shoudComponentUpdate 为 true
  useEffect(() => {
    console.log('接收名字:' + props.name)
  }, [props.name])

  return (
    <div>
      <h1>子组件</h1>
      <p>name:{props.name}</p>
      <p>mess:{props.mess}</p>
    </div>
  )
}
const Demo = () => {
  const [name, setName] = useState('王朗')
  const [message, setMess] = useState('hello,baby')

  const changeName = () => {
    setName(Math.random(0, 1) * 100)
  }
  const changeMes = () => {
    setMess('world')
  }

  return (
    <div>
      <p>{name}</p>
      <Child name={name} mess={message} />
      <button onClick={changeName}>name更新</button>
      <button onClick={changeMes}>mess更新</button>
    </div>
  )
}

export default Demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
  • 可以传入依赖变化的 props 的值,来决定要不要更新,如果不传, 就会一直执行, 而 mess 更新了, 则不会引起子组件的响应

# useReducer 和 context 实现 redux 逻辑

实现模拟一个后台管理系统的侧边栏, tag 标签卡, 头部菜单信息

容器组件

import React, { useReducer, createContext } from 'react'
import Header from './views/Header'
import Side from './views/Side'
import Tag from './views/Tag'
// 创建上下文
export const Context = createContext(null)

export const defaultState = {
  list: [
    {
      path: '/home',
      title: '首页'
    }
  ],
  activedMenu: {
    path: '/home',
    title: '首页'
  }
}
export function reducer(state, action) {
  switch (action.type) {
    case 'ADD_DATA':
      let isHas = state.list.some(item => item.path === action.payload.path)
      let newList = state.list
      if (!isHas) {
        newList.push(action.payload)
      }
      return { ...state, list: newList, activedMenu: action.payload }
    default:
      throw new Error()
  }
}

const Demo = () => {
  const [state, dispatch] = useReducer(reducer, defaultState)
  return (
    <Context.Provider value={{ state, dispatch }}>
      <Header></Header>
      <Side></Side>
      <Tag />
    </Context.Provider>
  )
}
export default Demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

Header 组件

import React, { useContext } from 'react'
import { Context } from './../Demo'
const Header = () => {
  const { state } = useContext(Context)
  const { title } = state.activedMenu
  return <div>header:{title}</div>
}

export default Header
1
2
3
4
5
6
7
8
9

Side 组件

import React, { useContext } from 'react'
import { Context } from './../Demo'
const list = [
  {
    path: '/home',
    title: '首页'
  },
  {
    path: '/about',
    title: '关于'
  },
  {
    path: '/mine',
    title: '我的'
  }
]
const Side = () => {
  const { dispatch } = useContext(Context)
  const handleClick = item => {
    dispatch({
      type: 'ADD_DATA',
      payload: item
    })
  }
  return (
    <div>
      <p>sidebar:</p>
      <ul>
        {list.map(item => (
          <li
            key={item.title}
            path={item.path}
            onClick={() => handleClick(item)}
          >
            {item.title}
          </li>
        ))}
      </ul>
    </div>
  )
}

export default Side
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

Tag 组件

import React, { useContext } from 'react'
import { Context } from './../Demo'
const Tag = () => {
  const { state } = useContext(Context)
  console.log(state)
  return (
    <div>
      <p>Tags标签页</p>
      {state.list.map(item => (
        <span key={item.title}>{item.title}</span>
      ))}
    </div>
  )
}

export default Tag
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16