揭开React Hooks神秘面纱

useState

例子

// useState 中的函数只会执行一次
function App (props) {
  const [ count, setCount ] = useState(() => {
    return props.count || 0
  })

  return (
    <div>
      点击次数: { count } 
      <button onClick={() => { setCount(count + 1)}}>点我</button>
    </div>
    )
}

不要在循环,条件或嵌套函数中调用 Hook

useEffect

例子

// useState 中的函数只会执行一次
function App () {
  const [ count, setCount ] = useState(0)

  useEffect(() => {
    document.title = count
  })

  return (
    <div>
      页面名称: { count } 
      <button onClick={() => { setCount(count + 1 )}}>点我</button>
    </div>
    )
}

useEffect 的第二个参数

useEffect 的第二个参数,有三种情况

  1. 什么都不传,组件每次 render 之后 useEffect 都会调用,相当于 componentDidMount 和 componentDidUpdate
  2. 传入空数组[], 只会调用一次,相当于 componentDidMount 和 componentWillUnmount
  3. 传入一个数组,其中包括变量,只有这些变量变动时,useEffect 才会执行
function App () {
  const [ count, setCount ] = useState(0)
  const [ width, setWidth ] = useState(document.body.clientWidth)

  const onChange = () => {
    setWidth(document.body.clientWidth)
  }

  useEffect(() => {
    // 相当于 componentDidMount
    console.log('add resize event')
    window.addEventListener('resize', onChange, false)

    return () => {
      // 相当于 componentWillUnmount
      window.removeEventListener('resize', onChange, false)
    }
  }, [])

  useEffect(() => {
    // 相当于 componentDidUpdate
    document.title = count
  })

  useEffect(() => {
    console.log(`count change: count is ${count}`)
  }, [ count ])

  return (
    <div>
      页面名称: { count } 
      页面宽度: { width }
      <button onClick={() => { setCount(count + 1)}}>点我</button>
    </div>
    )
}

useContext

React 中 Context 的使用

Context 提供了一种方式,能够让数据在组件树中传递时不必一级一级的手动传递

import React, { createContext } form 'react'
// 创建Context
const ThemeContext = createContext()

class App extends React.Component {
  state = {
    theme: 'red'
  }
  render () {
    const { theme } = this.state
    return (
      // 使用 Context.Provider传递value值 
      <ThemeContext.Provider value={theme}>
        {/* 当Context的Provider值更改时,Consumer 的值必须重新渲染 */}
        <button onClick={() => {this.setState({ theme: 'yellow'})}}>切换</button>
        <Middle></Middle>
      </ThemeContext.Provider>
    )
  }
}

class Middle extends React.Component {
  render () {
    return (
      <Result></Result>
    )
  }
}

class Result extends React.Component {
  render () {
    return (
      // 使用 Context.consumer接受(消费)value 
      <ThemeContext.consumer>
        {
          theme => <h1>ThemeContext的主题为 {theme}</h1>
        }
      </ThemeContext.consumer>
    )
  }
}

export default App

注意:context 类似于全局变量做法,会让组件失去独立性、复用起来更困难,不能滥用、但本身它一定有适合使用的场景,具体看情况使用

contextType

contextType 可以简化 context 的使用,不使用 consumer 也可以共享变量

import React, { createContext } from 'react'

// 创建Context
const ThemeContext = createContext()
const SizeContext = createContext()


class App extends React.Component {
  state = {
    theme: 'red',
    size: 'small'
  }
  render () {
    const { theme, size } = this.state
    return (
      // 使用 Context.Provider 包裹后续组件,value 指定值 
      <ThemeContext.Provider value={theme}>
        {/* 当出现多个Context的时候,只需要将Context.Provider 嵌套即可 */}
        <SizeContext.Provider value={size}>
          {/* 当Context的Provider值更改时,Consumer 的值必须重新渲染 */}
          <button onClick={() => {this.setState({ theme: 'yellow', size: 'big'})}}>按钮</button>
          <Middle></Middle>
        </SizeContext.Provider>
      </ThemeContext.Provider>
    )
  }
}

class Bottom extends React.Component {
  // 申明静态变量、contextType 将 context 直接赋值于 contextType
  static contextType = ThemeContext
  
  render () {
    // 在 render 函数中 可以直接 访问 this.context 获取共享变量、这样就可以不使用 consumer
    const theme = this.context
    return (
      // Context.Consumer Consumer消费者使用Context得值
      // 但子组件不能是其他组件,必须渲染一个函数,函数的参数就是Context得值
      // 当出现 多个Consumer的时候,进行嵌套,每个Consumer 的子组件必须是一个函数,即可
      <div>
        <h1>ThemeContext 的 值为 {theme} </h1>
      </div>
    )
  }
}

class Middle extends React.Component {
  render () {
    return <Bottom></Bottom>
  }
}

export default App;

注意

  1. contextType 只能在类组件中使用
  2. 一个组件如果有多个 consumer , contextType 只对其中一个有效,所以说,contextType 只能有一个
  3. context中的provider和consumer,在类组件以及函数组件中都可以使用。但是contextType只能在类组件中使用,因为它是类的静态属性。

useContext在函数组件中的应用

import React, { createContext, useContext, useState } from 'react'
// 创建一个context
const Context = createContext(0)
// 1. consumer写法
class Item1 extends PureComponent {
  render () {
    return (
      <Context.consumer>
        {
          count => (<div>{count}</div>)
        }
      </Context.consumer>
    )
  }
}

// 2. contextType写法
class Item2 extends PureComponent {
  static contextType = Context
  render () {
    const count = this.context
    return (
      <div>{count}</div>
    )
  }
}

// 3. useContext写法
function Item3 () {
  const count = useContext(Context)

  render() {
    return (
      <div>{count}</div>
    )
  }
}

function App() {
  const [ count, setCount ] = useState(0)

  render() {
    return (
      <div>
        点击次数: { count } 
        <div onClick={() => {setCount(count + 1)}}>点击</div>
        <Context.Provider value={count}>
          <Item1></Item1>
          <Item2></Item2>
          <Item3></Item3>
        </Context.Provider>
      </div>
    )
  }
}
 export default App

consumer / contextType / useContext 对比学习

  • consumer 嵌套复杂,consumer的第一个子节点必须是一个函数
  • contextType 只支持类组件的写法,且只能使用一次(不支持多个context)
  • useContext 不需要嵌套,写法简单

ref

Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素(useRef())

useRef()

import React, { useRef } from 'react'
const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref} className="FancyButton">
    {props.children}
  </button>
));

const ref = React.useRef()
<FancyButton ref={ref}>Click me!</FancyButton>;

React.createRef()
React.forwardRef()

const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref} className="FancyButton">
    {props.children}
  </button>
));

const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
  1. 通过调用 React.createRef() 创建了一个 React ref 并将其赋值给 ref 变量
  2. 通过指定 ref 为 JSX 属性,将其向下传递给
  3. React 传递 ref 给 forwardRef 内函数 (props, ref) => …,作为其第二个参数
  4. 向下转发该 ref 参数到 ,将其指定为 JSX 属性
  5. 当 ref 挂载完成,ref.current 将指向 DOM 节点

React.forwardRef((props, ref) => (…)) ref参数只在使用 React.forwardRef 定义组件时存在

useReducer

const [state, dispatch] = useReducer(reducer, initialArg, init)
reducer接受类型为 (state, action) => newState,并返回与 dispatch 方法配对的当前状态
initialArg初始化元素
useReduceruseState的替代方案

import React, { useReducer } from 'react'

function reducer (count, action) {
  switch (action.type) {
    case 'increment':
      return count + 1
    case 'decrement':
      return count - 1
    case 'reset':
      return 0
    default:
      throw new Error()
  }
}

function Home () {
  const [ count, setCount ] = useReducer(reducer, 0)

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount({type: 'reset'})}>Reset</button>
      <button onClick={() => setCount({type: 'increment'})}>+</button>
      <button onClick={() => setCount({type: 'decrement'})}>-</button>
    </div>
  )
}

export default Home

惰性初始化

// 优化:延迟初始化
import React, { useReducer } from 'react'

function init(initialCount) {
    return {count: initialCount}
  }
  
function reducer(state, action) {
    switch (action.type) {
        case 'increment':
            return {count: state.count + 1}
        case 'decrement':
            return {count: state.count - 1}
        case 'reset':
            return init(action.payload)
        default:
            throw new Error()
    }
}

function Home ({initialCount = 0}) {
    const [state, dispatch] = useReducer(reducer, initialCount, init)
    return (
        <div>
            Count: {state.count}
            <br />
            <button
                onClick={() => dispatch({type: 'reset', payload: initialCount})}>
                Reset
            </button>
            <button onClick={() => dispatch({type: 'increment'})}>+</button>
            <button onClick={() => dispatch({type: 'decrement'})}>-</button>
        </div>
    )
}

export default Home

useReducer与useState的区别

  • 当 state 状态值结构比较复杂时,使用 useReducer 更有优势
  • 使用 useState 获取的 setState 方法更新数据时是异步的, 而使用 useReducer 获取的 setState 方法更新数据是同步的

实例: 测试数据是否可以连续更新两次

使用useState验证

import React, { useState } from 'react'

function Home () {
  const [ count, setCount ] = useState(0)

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(0)}>Reset</button>
      <button onClick={() => setCount(count + 1)}>+</button>
      <button onClick={() => setCount(count - 1)}>-</button>
      <button onClick={() => {
        setCount(count + 1)
        setCount(count + 1)
      }}>测试能否连续更新两次</button>
    </div>
  )
}

export default Home

结论: 点击 测试能否连续更新两次 按钮,会发现点击一次 count 还是只增加了 1. 由此可见,useState 确实是 异步 更新数据

使用useReducer验证

import React, { useReducer } from 'react'
import { injectIntl } from 'react-intl'

function reducer (count, action) {
  switch (action.type) {
    case 'increment':
      return count + 1
    case 'decrement':
      return count - 1
    case 'reset':
      return 0
    default:
      throw new Error()
  }
}

function Home () {
  const [ count, setCount ] = useReducer(reducer, 0)

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount({type: 'reset'})}>Reset</button>
      <button onClick={() => setCount({type: 'increment'})}>+</button>
      <button onClick={() => setCount({type: 'decrement'})}>-</button>
      <button onClick={() => {
        setCount({type: 'increment'})
        setCount({type: 'increment'})
      }}>测试能否连续更新两次</button>
    </div>
  )
}

export default injectIntl(Home)

结论: 点击 测试能否连续更新两次 按钮,会发现点击一次 count 增加了 2. 由此可见,每次dispatch 一个action就会更新一次数据. useReducer 确实是 同步 更新数据

实例:根据不同的操作显示隐藏不同的子组件

import React, { useReducer } from 'react'
import Create from "./compontents/create.tsx";
function reducer(visible: any, action: any) {
  switch (action.type) {
    case "create":
      return {
        ...visible,
        createDisk: !visible.createDisk
      };
    case "attach":
      return {
        ...visible,
        attachDisk: !visible.attachDisk
      };
    case "unattach":
      return {
        ...visible,
        unattachDisk: !visible.unattachDisk
      };
    default:
      throw new Error();
  }
}
const [visible, setVisible]: any = useReducer(reducer, {
    createDisk: false,
    attachDisk: false,
    unattachDisk: false
  });
const handleOperation = (type: String) => {
    switch (type) {
      case "create":
        setVisible({type: 'create'})
        break;
      case "attach":
        setVisible({type: 'attach'})
        break;
      case "unattach":
        setVisible({type: 'unattach'})
        break;
      default:
        break;
    }
  };
  return (
  	<div className="container>
  		<a-button onClick={() => handleOperation('create')}>创建</a-button>
  		<a-button onClick={() => handleOperation('attach')}>挂载</a-button>
  		<a-button onClick={() => handleOperation('unattach')}>卸载</a-button>
  		<Create visible={visible.createDisk} setVisible={setVisible} />
  	</div>
  )
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值