React-Hooks 和 React-Redux

发布于:2023-09-16 ⋅ 阅读:(60) ⋅ 点赞:(0)

注:Redux最新用法参考 个人React专栏 react 初级学习

Hooks基本介绍-------------------------

  • Hooks:钩子、钓钩、钩住, Hook 就是一个特殊的函数,让你在函数组件中获取状态等 React 特性 ,是 React v16.8 中的新增功能

  • 作用:为函数组件提供状态、生命周期等原本 class 组件中提供的 React 功能

    • 可以理解为通过 Hooks 为函数组件钩入 class 组件的特性

  • 注意:Hooks 只能在函数组件中使用,自此,函数组件成为 React 的新宠儿

React v16.8 版本前后,组件开发模式的对比:

  • React v16.8 以前: class 组件(提供状态) + 函数组件(展示内容)

  • React v16.8 及其以后:

    1. class 组件(提供状态) + 函数组件(展示内容)

    2. Hooks(提供状态) + 函数组件(展示内容)

    3. 混用以上两种方式:部分功能用 class 组件,部分功能用 Hooks+函数组件

总结

注意1:虽然有了 Hooks,但 React 官方并没有计划从 React 库中移除 class

注意2:有了 Hooks 以后,不能再把函数组件称为无状态组件了,因为 Hooks 为函数组件提供了状态

为什么要有 Hooks

  • 组件的状态逻辑复用
    • 在 Hooks 之前,组件的状态逻辑复用经历了:mixins(混入)、HOCs(高阶组件)、render-props 等模式

    • (早已废弃)mixins 的问题:1 数据来源不清晰 2 命名冲突

    • HOCs、render-props 的问题:重构组件结构,导致组件形成 JSX 嵌套地狱问题

  • class 组件自身的问题
    • 选择:函数组件和 class 组件之间的区别以及使用哪种组件更合适

    • 需要理解 class 中的 this 是如何工作的

    • 相互关联且需要对照修改的代码被拆分到不同生命周期函数中

    • 相比于函数组件来说,不利于代码压缩和优化,也不利于 TS 的类型推导

注意:

之前的react语法并不是以后就不用了,class 组件相关的 API 在hooks中可以不用

  • class 自身语法,比如,constructor、static 等
  • 钩子函数,componentDidMountcomponentDidUpdatecomponentWillUnmount
  • this 相关的用法

useState-Hooks

useState-基本使用

  • useState作用:为函数组件提供状态(state),不能在类组件中调用

  • useState使用场景:当你想要在函数组件中,使用组件状态时,就要使用 useState Hook 了

  • 约定:修改状态的函数名称以 set 开头,后面跟上状态的名称

  • 多次调用 useState 多个状态和修改状态的函数之间不会相互影响

  • useState 提供的状态,是函数内部的局部变量,可以在函数内的任意位置使用

  • 每次渲染,useState 获取到的都是最新的状态值(react会记住最新的状态值),useState 的初始值(参数)只会在组件第一次渲染时生效

语法:

import { useState } from 'react'

// 参数:状态初始值可以是任意值
// 返回值:stateArray 是一个数组
const stateArray = useState(0)

// 索引 0 表示:状态值(state)
const state = stateArray[0]
// 索引 1 表示:修改状态的函数(setState(newValue)` 是一个函数,参数表示:*新的状态值*)
const setState = stateArray[1]

状态的读取和修改:

  • 读取状态

const Counter = () => {
  const [user, setUser] = useState({ name: 'jack', age: 18 })
  
  return (
      <div>
        <p>姓名:{user.name}</p>
            <p>年龄:{user.age}</p>
    </div>
  )
}

  • 修改状态
    • 调用该函数后,将使用新的状态值替换旧值
    • 修改状态后,因为状态发生了改变,所以,该组件会重新渲染
    • setUser(newValue) 是一个函数,参数表示:新的状态值 。调用这个函数,新的状态值会覆盖原来的状态值,所以,这里和class的setState不一样,区分一下,class的setState需要修改哪个值就传哪个值,内部会做状态的合并,hooks呢修改状态值会直接覆盖不会合并,所以hooks修改状态值需要先对原来的值进行取值解构,再修改。
const Counter = () => {
  // 利用数组解构提取状态和修改状态
  const [user, setUser] = useState({ name: 'jack', age: 18 })
  
  const onAgeAdd = () => {
    setUser({
      ...user,
      age: user.age + 1
    })
  }
  
  return (
  	<div>
    	<p>姓名:{user.name}</p>
			<p>年龄:{user.age}</p>
     	<button onClick={onAgeAdd}>年龄+1</button>
    </div>
  )
}

useState-使用规则

  • 如何为函数组件提供多个状态?

    • 调用 useState Hook 多次即可,每调用一次 useState Hook 可以提供一个状态

    • useState Hook 多次调用返回的 [state, setState],相互之间,互不影响

  • useState 等 Hook 的使用规则:

    • React Hooks 只能直接出现在 函数组件 中

    • React Hooks不能嵌套在 if/for/其他函数 中

    • 原理:React 是按照 Hooks 的调用顺序来识别每一个 Hook,如果每次调用的顺序不同,导致 React 无法知道是哪一个 Hook

useEffect-Hooks

useEffect-基本含义

  • 作用:处理函数组件中的副作用(side effect)
  • 副作用是相对于主作用来说的,一个功能(比如,函数)除了主作用,其他的作用就是副作用 对于 React 组件来说,主作用就是根据数据(state/props)渲染 UI,除此之外都是副作用(比如,手动修改 DOM)
  • 常见的副作用(side effect):数据(Ajax)请求、手动修改 DOM、localStorage、console.log 操作等
  • useEffect完全指南:useEffect 完整指南 — Overreacted

useEffect-基本使用

  • 使用时机

import { useEffect } from 'react'

// 1 
// 触发时机: 第一次渲染会执行 、 每次组件重新渲染都会再次执行
// componentDidMount + ComponentDidUpdate
useEffect(() => {})

// 2(使用频率最高)
// 触发时机:只在组件第一次渲染时执行
// componentDidMount
useEffect(() => {}, [])

// 3(使用频率最高)
// 触发时机:1 第一次渲染会执行 2 当 count(某个状态) 变化时才会次执行
// componentDidMount + componentDidUpdate(判断 count 有没有改变)
useEffect(() => {}, [count])

// 4
useEffect(() => {
  // 返回值函数的执行时机:组件卸载时
  // 在返回的函数中,清理工作
  return () => {
  	// 相当于 componentWillUnmount
  }
}, [])

//5
useEffect(() => {
  
  // 返回值函数的执行时机:1 组件卸载时 2 count 变化时
  // 在返回的函数中,清理工作
  return () => {}
}, [count])
  • 发送请求

  • 在组件中,可以使用 useEffect Hook 来发送请求(side effect)获取数据

  • 注意:effect 只能是一个同步函数,不能使用 async

    • 因为如果 effect 是 async 的,此时返回值是 Promise 对象。这样的话,就无法保证清理函数被立即调用

  • 为了使用 async/await 语法,可以在 effect 内部创建 async 函数,并调用

// 错误演示:不要给 effect 添加 async
useEffect(async () => {
  const res = await axios.get('http://xxx')
  return () => {}
}, [])

// 正确使用
useEffect(() => {
  const loadData = async () => {
    const res = await axios.get('http://xxx')
  }
  loadData()
  
  return () => {}
}, [])

useContext-Hooks

作用

  • 在函数组件中,获取 Context 中的值。要配合 Context 一起使用。

语法

  • useContext 的参数:Context 对象,即:通过 createContext 函数创建的对象

  • useContext 的返回值:Context.Provider 中提供的 value 数据

import { useContext } from 'react'

const { color } = useContext(ColorContext)

useContext Hook<Context.Consumer> 的区别:获取数据的位置不同

  • <Context.Consumer>:在 JSX 中获取 Context 共享的数据

  • useContext:在 JS 代码 或者 JSX 中获取 Context 的数据

const ColorContext = createContext()

const Child = () => {
  // 在普通的 JS 代码中:
  const { color } = useContext(ColorContext)

  return (
    <div>
      useContext 获取到的 color 值:{ color }
      {/* 在 JSX 中: */}
    	<ColorContext.Consumer>
        {color => <span>共享的数据为:{color}</span>}
      </ColorContext.Consumer>
    </div>
  )
}

useState-useRef

useRef-用法

  • 参数:在获取 DOM 时,一般都设置为 null(获取 DOM 对象时,如果拿不到 DOM 对象,此时,获取到的值就是 null)

  • 返回值:包含 current 属性的对象。

  • 注意:只要在 React 中进行 DOM 操作,都可以通过 useRef Hook 来获取 DOM(比如,获取 DOM 的宽高等)

  • 注意:useRef不仅仅可以用于操作DOM,还可以操作组件

import { useRef } from 'react'

const App = () => {
  // 1 使用useRef能够创建一个ref对象
  const inputRef = useRef(null)

  const add = () => {
    // 3 通过 inputRef.current 来访问对应的 DOM
    console.log(inputRef.current.value)
    inputRef.current.focus()
  }
  
  return (
    <section className="todoapp">
      {/* 2 将 ref 对象设置为 input 的 ref 属性值。目的:将 ref 对象关联到 input 对应的 DOM 对象上 */}
      <input type="text" placeholder="请输入内容" ref={inputRef} />
      <button onClick={add}>添加</button>
    </section>
  )
}

export default App

Redux基本介绍------------------------

作用:集中式存储和管理应用的状态、处理组件通讯问题时,无视组件之间的层级关系、简化大型复杂应用中组件之间的通讯问题、数据流清晰,易于定位 Bug

Redux核心概念

为了让代码各部分职责清晰、明确,Redux 代码被分为三个核心概念:action/reducer/store

action(动作)

  • 描述要做的事情,特点:只描述做什么

  • 是一个js对象必须带有type属性,用于区分动作的类型

  • 根据功能的不同,可以携带额外的payload有效的载荷参数数据来完成相应功能

  • 例如 计数器功能描述
    { type: 'increment', payload: 10 } // +10
    { type: 'decrement', payload: 10 } // -10
  •  为了使action功能 多元并且灵活化,需要使用 action creator 函数去创建action,目的:简化多次使用 action 时,重复创建 action 对象,函数返回值依然是个action对象。
  • const decrement = payload => ({ type: 'decrement', payload })

reducer(函数)

  • 用来处理 action 并更新状态,是 Redux 状态更新的地方
  • 函数签名为:(prevState, action) => newState

  • 接收上一次的状态和 action 作为参数,根据 action 的类型,执行不同操作,最终返回新的状态,注意:该函数一定要有返回值,即使状态没有改变也要返回上一次的状态

  • 约定:reducer 是一个纯函数(相同的输入总是得到相同的输出),并且不能包含 side effect 副作用(比如,不能修改函数参数、不能修改函数外部数据、不能进行异步操作等)

  • 对于 reducer 来说,为了保证 reducer 是一个纯函数,不要:

    1. 不要直接修改参数 state 的值(也就是:不要直接修改当前状态,而是根据当前状态值创建新的状态值)

    2. 不要使用 Math.random() / new Date() / Date.now() / ajax 请求等不纯的操作

    3. 不要让 reducer 执行副作用(side effect)

  • // 示例:
    // state 上一次的状态
    // action 当前要执行的动作
    const reducer = (state=10, action) => {
      switch (action.type) {
        // 计数器增加
        case 'increment':
          // 返回新状态
          // return state + 1
          // 根据 action 中提供的 payload 来决定到底增加多少
          return state + action.payload
          // 注意:一定要有 default,如果将来 reducer 无法处理某个 action,
             就直接将上一次的状态返回即可
        default:
          return state
      }
    }

store(仓库)

  • 整合 action 和 reducer,一个应用只有一个 store

  • 维护应用的状态,获取状态:store.getState()

  • 发起状态更新时,需要分发 action:store.dispatch(action)

  • 创建 store 时接收 reducer 作为参数const store = createStore(reducer)

  • 订阅(监听)状态变化:const unSubscribe = store.subscribe(() => {})

  • 取消订阅状态变化: unSubscribe()

核心代码

import {createStore} from 'redux'

// 创建 store
// 参数为:reducer 函数
const store = createStore(reducer)

// 更新状态
// dispatch 派遣,派出。表示:分发一个 action,也就是发起状态更新
store.dispatch(action)
store.dispatch( increment(2) )

// 获取状态
const state = store.getState()

// 其他 API------
// 监听状态变化
const unSubscribe = store.subscribe(() => {
  // 状态改变时,执行相应操作
  // 比如,记录 redux 状态
  console.log(store.getState())
})

// 取消监听状态变化
unSubscribe()

Redux获取状态默认值执行过程

  • 只要创建 store,给createStore(传递了 reducer),那么,Redux 就会调用一次 reducer
  • Redux 内部第一次调用 reducer: reducer的默认传值的type和payload为     (undefined, {type: "@@redux/INITv.a.4.t.t.p"})
  • 因为传入的状态值是 undefined ,并且是一个随机的 action type,因为是一个随机的 action type,所以,reducer 中 switch 一定无法处理该 action,那就一定会走 default。也就是直接返回了状态的默认值:10
  • Redux 内部拿到状态值store.getState()(比如,此处的 10)以后,就用这个状态值,来作为了 store 中状态的默认值

Redux代码执行流程

  1. 创建 store 时,Redux 就会先调用一次 reducer,来获取到默认状态(10)

  2. 然后分发动作 store.dispatch(action)更新状态

  3. 只要调用了dispatch操作,Redux store 内部就会调用 reducer 传入:上一次的状态(当前示例中就是:10)和当前传入的 action({ type: 'increment' }),计算出新的状态并返回

  4. reducer 执行完毕后,将最新的状态交给 store,store 用最新的状态替换旧状态,状态更新完毕

import { createStore } from 'redux'
const store = createStore(reducer)

// reducer(10, { type: 'increment' })
function reducer(state = 10, action) {
  console.log('reducer:', state, action)
  switch (action.type) {
    case 'increment':
      return state + 1
    default:
      return state
  }
}

console.log('状态值为:', store.getState()) // 10

// 发起更新状态:
// 参数: action 对象
store.dispatch({ type: 'increment' })
// 相当于: reducer(10, { type: 'increment' })

console.log('更新后:', store.getState()) // 11

React-Redux介绍

react-redux 库是 Redux 官方提供的 React 绑定库,为 React 接入 Redux,实现在 React 中使用 Redux 进行状态管理。 React 和 Redux 是两个独立的库,两者之间职责独立。因此,为了实现在 React 中使用 Redux 进行状态管理 ,就需要一种机制,将这两个独立的库关联在一起。这时候就用到 React-Redux 这个绑定库了。

react-redux 文档

react-redux 的使用分为两大步:

1 全局配置(只需要配置一次)

  1. 安装 react-redux:  yarn add react-redux  

  2. 从 react-redux 中导入 Provider 组件

  3. 导入创建好的 redux 仓库

  4. 使用 Provider 包裹整个应用

  5. 将导入的 store 设置为 Provider 的 store 属性值

index.js

// 导入 Provider 组件
import { Provider } from 'react-redux'
// 导入创建好的 store
import store from './store'

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.querySelector('#root')
)

2 组件接入(获取状态或修改状态)

获取状态:useSelector hook

  • useSelector:获取 Redux 提供的状态数据

  • 参数:selector 函数,用于从 Redux 状态中筛选出需要的状态数据并返回

  • 返回值:筛选出的状态

import { useSelector } from 'react-redux'

const App = () => {
  const count = useSelector(state => state)
  
  return (
  	<div>
    	<h1>计数器:{count}</h1>
      <button>数值增加</button>
			<button>数值减少</button>
    </div>
  )
}

修改状态:useDispatch hook

  • useDispatch:拿到 dispatch 函数,分发 action,修改 redux 中的状态数据

import { useDispatch } from 'react-redux'

const App = () => {
  const dispatch = useDispatch()
  
  return (
  	<div>
    	<h1>计数器:{count}</h1>
      {/* 调用 dispatch 分发 action */}
      <button onClick={() => dispatch(increment(2))}>数值增加</button>
			<button onClick={() => dispatch(decrement(5))}>数值减少</button>
    </div>
  )
}

总结

  • 任何一个组件都可以直接接入 Redux,也就是可以直接:1 修改 Redux 状态 2 接收 Redux 状态

  • 并且,只要 Redux 中的状态改变了,所有接收该状态的组件都会收到通知,也就是可以获取到最新的 Redux 状态

  • 这样的话,两个组件不管隔得多远,都可以直接通讯了  

redux数据流动过程:

Redux应用

代码结构

 /store        --- 在 src 目录中创建,用于存放 Redux 相关的代码
   /actions    --- 存放所有的 action
   /reducers   --- 存放所有的 reducer
   index.js    --- redux 的入口文件,用来创建 store

Redux应用

ActionType

  • Action Type 指的是:action 对象中 type 属性的值

  • Redux 项目中会多次使用 action type,比如,action 对象、reducer 函数、dispatch(action) 等

  • 目标:集中处理 action type,保持项目中 action type 的一致性

  • action type 的值采用:'domain/action'(功能/动作)形式,进行分类处理,比如,

    • 计数器:'counter/increment' 表示 Counter 功能中的 increment 动作

    • 登录:'login/getCode' 表示登录获取验证码的动作

    • 个人资料:'profile/get' 表示获取个人资料

步骤

  1. 在 store 目录中创建 actionTypes 目录或者 constants 目录,集中处理

  2. 创建常量来存储 action type,并导出

  3. 将项目中用到 action type 的地方替换为这些常量,从而保持项目中 action type 的一致性

// actionTypes 或 constants 目录:

const increment = 'counter/increment'
const decrement = 'counter/decrement'

export { increment, decrement }

// --

// 使用:

// actions/index.js
import * as types from '../acitonTypes'
const increment = payload => ({ type: types.increment, payload })
const decrement = payload => ({ type: types.decrement, payload })

// reducers/index.js
import * as types from '../acitonTypes'
const reducer = (state, action) => {
  switch (action.type) {
    case types.increment:
      return state + 1
    case types.decrement:
      return state - action.payload
    default:
      return state
  }
}
  • 注:额外添加 Action Type 会让项目结构变复杂,此操作可省略 ( 大型项目推荐使用 ) 。但,domain/action 命名方式强烈推荐!

Redux应用

Reducer的分离与合并

  • 随着项目功能变得越来越复杂,推荐 使用多个 reducer:按照项目功能划分,每个功能使用一个 reducer 来处理该功能的状态更新
  • 项目中会有多个 reducer,但是 store 只能接收一个 reducer,因此,需要将多个 reducer 合并为一根 reducer,才能传递给 store
  • 合并方式:使用 Redux 中的 combineReducers 函数

  • 注意:合并后,Redux 的状态会变为一个对象,对象的结构与 combineReducers 函数的参数结构相同

    • 比如,此时 Redux 状态为:{ a: aReducer 处理的状态, b: bReducer 处理的状态 }

  •  整个 Redux 应用的状态变为了对象,但是,对于每个 reducer 来说,每个 reducer 只负责整个状态中的某一个值,每个reducer只负责自己要处理的状态

  • 合并 reducer 后,redux 处理方式:只要合并了 reducer,不管分发什么 action,所有的 reducer 都会执行一次。各个 reducer 在执行的时候,能处理这个 action 就处理,处理不了就直接返回上一次的状态。所以,我们分发的某一个 action 就只能被某一个 reducer 来处理,也就是最终只会修改这个 reducer 要处理的状态,最终的表现就是:分发了 action,只修改了 redux 中这个 action 对应的状态!

import { combineReducers } from 'redux'

// 计数器案例,状态默认值为:0
const aReducer = (state = 0, action) => {}
// Todos 案例,状态默认值为:[]
const bReducer = (state = [], action) => {}

// 合并多个 reducer 为一个 根reducer
const rootReducer = combineReducers({
  aReducer: aReducer,
  bReducer: bReducer
})

// 创建 store 时,传入 根reducer
const store = createStore(rootReducer)

// 此时,合并后的 redux 状态: { a: 0, b: [] }



reducer状态合并后,再次访问每个状态的时候,这个状态就是合并后的对象了,需要.上对象访问状态
import { useSelector } from 'react-redux'

const App = () => {
  const count = useSelector(state => state.aReducer)
  const list = useSelector(state => state.bReducer)
}

注释:(个人针对redux的理解叙述)

  • const increment= payload => ({ type: 'increment', payload })
  • redux作为一个状态管理工具,将数据的状态分为了三部分,action、reducers和store
  • action作为一个描述数据用途的对象, { type: 'increment', payload: 10 },这里可以将这个用途对象理解为一个标志,increment就是计数器增加的标志,表示将来需要增加的标识
  • 当我们进行点击行为增加数据的时候,会调用store就会调用这个增加的标识store.dispacth(increment())
  • store.dispatch发起状态更新后,只要调用了dispatch操作,Redux store 内部就会调用 reducer 并且传入上一次的状态和当前传入的 action标识,相当于会给reduce分发一个状态标志"increment",从而使用switch函数进行标识的配对,分发正确的逻辑内容,计算出新的状态并返回
  • reducer 执行完毕后,将最新的状态交给 store,store 用最新的状态替换旧状态,状态更新完

注:

redux在这里 createStore 的使用已经过时,想要了解数据分发传递的实验过程可以参考上面,新的用法逻辑提现在新的文章中...

本文含有隐藏内容,请 开通VIP 后查看