React(三)useReducer、useContext钩子的使用,redux库的初步使用

目录

(一)useReducer

1.什么是useReducer?

2.useReducer的使用

3.useReducer的踩雷点

(1)使用dispatch()后发现state打印出来没有立即更新

(2)在reducer里没有返回新的对象或数组会导致react跳过这次dispatch

(3)reducer 和初始化函数运行了两次 

(二)useContext

1. 什么是useContext?

2.useContext的使用

3.useContext的踩坑点

(1)当使用多个context.provider标签包裹子组件时

(2)当没有使用 context.provider标签时

(3)在传递对象和函数时优化渲染

(三)Redux 

1.redux的使用

(1)创建store

(2)在组件中使用

(3)拆分组件

2.reduxjs/toolkit的使用

(1)创建store

(2)创建reducer

(3)将reducer引入到store中,在组件中使用

(四)总结


(一)useReducer

useReducer – React 中文文档

1.什么是useReducer?

useReducer是一个用于集中管理状态的 react Hook,通过dispatch函数派发类型和数据给actions函数,达到修改state的目的

接收一个reducer函数、state初始值、初始化参数init(可选),返回一个state数据和dispatch函数,相当于是useState的plus版,reducer函数就是我们自定义的处理state数据的方法。

2.useReducer的使用

以计数器为例

引入useReducer

import { useReducer } from "react"

创建reducer函数,传入参数state、action

通过action的type属性确定执行不同的函数体

function reducer(state,action){
  switch(action.type){
    case 'increment':{
      return {
        ...state,
        num:state.num + action.num
      }
    }
    case 'decrement':{
      return {
        ...state,
        num:state.num - action.num
      }
    }
    default:{
      return state
    }
  }
}

通过dispatch函数传入action对象:{type:'xxx', data:xxx} 

function App() {
  const [state, dispatch] = useReducer(reducer, {num:1})

  return (
    <>
      <div>{state.num}</div>
      <button onClick={() => { dispatch({ type: 'increment',num:5})}}>+5</button>
      <button onClick={() => { dispatch({ type: 'decrement', num: 5 }) }}>-5</button>
    </>
  )
}

3.useReducer的踩雷点

(1)使用dispatch()后发现state打印出来没有立即更新

state和useState的效果一样,是异步更新的,需要等到下一次dom改变才会更新,解决办法就是在dom中使用到state的数据

(2)在reducer里没有返回新的对象或数组会导致react跳过这次dispatch

React 使用 Object.is 比较更新前后的 state,如果 它们相等就会跳过这次更新。 

// 直接更改对象或数组
case 'add':{
    state.name = 'csq'
    return state
}

Object.is 运作如下:

因此修改为:

case 'incremented_age': {
    // 创建一个新的对象
    return {
        ...state,
        name: 'csq'
    };

(3)reducer 和初始化函数运行了两次 

严格模式 下 React 会调用两次 reducer 和初始化函数,但是这不应该会破坏代码逻辑。

这个 仅限于开发模式 的行为可以帮助你 保持组件纯粹:React 会使用其中一次调用结果并忽略另一个结果。如果你的组件、初始化函数以及 reducer 函数都是纯函数,这并不会影响你的逻辑。不过一旦它们存在副作用,这个额外的行为就可以帮助你发现它。

(二)useContext

useContext – React 中文文档

1. 什么是useContext?

useContext是一个用于读取和订阅组件中的context的 Hook

组件嵌套过深又需要传递数据时,一层一层的传props是不现实的,Context 允许父组件向其下层无论多深的任何组件提供信息,而无需通过 props 显式传递。

这样就需要useContext,在顶层组件创建context,将context标签包裹住需要传值的子组件,这样嵌套在里面的组件就可以获取到这个context的值了

2.useContext的使用

以点击复选框改变form和button的主题色为例: 

引入createContext、useContext,并创建主题context,传入初始值dark(可以为空)

import { createContext, useContext} from "react"
const ThemeContext = createContext('dark')

建立组件,在顶部组件设置theme响应式,点击复选框改变theme的值

function App() {
  const [theme, setTheme] = useState('dark')

  return (
    <div className="main">
        <Form></Form>
        <label>
          <input
            type="checkbox"
            checked={theme === 'dark'}
            onChange={(e) => {
              setTheme(e.target.checked ? 'dark' : 'light')
            }}s
          />
          Use dark mode
        </label>
    </div>
  )
}

function Form() {

  return (
    <div className='form'>
      {/*  */}
      <Panel>
        <Button value={'登录'} ></Button>
        <Button value={'登出'} ></Button>
      </Panel>
    </div>
  )
}

function Panel({ children }) {

  return (
    <>
      <div className="panel">
        <h1>Welcome</h1>
        {children}
      </div>
    </>
  )
}

function Button({ value }) {

  return (
    <>
      <button>{value}</button>
    </>
  )
}

将顶部组件的theme传递给form组件和button组件:

用<ThemeContext.Provider>标签包裹需要获取context的组件,value={theme}传值给ThemeContext,需要的子组件再提供useContext()获取

function App() {
  const [theme, setTheme] = useState('dark')

  return (
    <div className="main">
      <ThemeContext.Provider value={theme}>
        <Form></Form>
        <label>
          <input
            type="checkbox"
            checked={theme === 'dark'}
            onChange={(e) => {
              setTheme(e.target.checked ? 'dark' : 'light')
            }}s
          />
          Use dark mode
        </label>
      </ThemeContext.Provider>
    </div>
  )
}

function Form() {
  const theme = useContext(ThemeContext)

  return (
    <div className={'form '+ theme}>
      {/*  */}
      <Panel>
        <Button value={'登录'} ></Button>
        <Button value={'登出'} ></Button>
      </Panel>
    </div>
  )
}
...

function Button({ value }) {
  const theme = useContext(ThemeContext)

  return (
    <>
      <button className={theme} >{value}</button>
    </>
  )
}

 这样就实现点击复选框改变主题颜色了!

3.useContext的踩坑点

(1)当使用多个context.provider标签包裹子组件时

子组件的useContext()会选取离他最近的provider的值

<ThemeContext.Provider value={1}>
    ...
    <ThemeContext.Provider value={2}>
        <Button></Button> // useContext获取到的是 2
    </ThemeContext.Provider>
</ThemeContext.Provider>

(2)当没有使用 context.provider标签时

useContext()获取到的值就是最开始createContext()的初始值 

(3)在传递对象和函数时优化渲染

function MyApp() {
  const [currentUser, setCurrentUser] = useState(null);

  function login(response) {
    storeCredentials(response.credentials);
    setCurrentUser(response.user);
  }

  return (
    <AuthContext.Provider value={{ currentUser, login }}>
      <Page />
    </AuthContext.Provider>
  );
}

每当 MyApp 出现重新渲染(例如,路由更新)时,这里将会是一个 不同的 对象指向 不同的 函数,因此 React 还必须重新渲染树中调用 useContext(AuthContext) 的所有组件。

如果基础数据如 currentUser 没有更改,则不需要重新渲染它们。为了帮助 React 利用这一点,你可以使用 useCallback 包装 login 函数,并将对象创建包装到 useMemo 中。

useCallBack和useMemo这两个钩子目前还没有学到,先挖个坑~~~

useContext – React 中文文档

(三)Redux 

介绍:redux是一个用于集中管理状态的库,类似于vue的store

redux原理跟reducer相同,dispatch派发action来更新,action是一个普通的JavaScript对象,用来描述这次更新的type和content。

1.redux的使用

下载react-redux:

npm install redux --save

不知道为啥我下面写的那些在组件里没有实现响应式。。。懵逼

哦是因为没有在组件里的mount时钟周期里订阅。。先放在这里 

(1)创建store

和useReducer一样,创建初始state对象、创建reducer,再初始化store,使用store.subscribe订阅state的变化

目前redux的createStore方法已经被redux/toolkit取代,后面会提及

import { createStore } from "redux";

// 初始state
const initialState = {
    num: 0
}
// 创建reducer 必须是纯函数 不能在里面直接修改state
const reducer = (state = initialState, action) => {
    switch (action.type) {
        case 'increment': {
            return {
                ...state,
                num: state.num + action.num
            }
        }
        case 'decrement': {
            return {
                ...state,
                num: state.num - action.num
            }
        }
        default: {
            return state
        }
    }
}
// createStore的写法已被取代
const store = createStore(reducer)

// 订阅state的变化
store.subscribe(() => {
    console.log("state发生了改变....", store.getState().num);
});

export default store

(2)在组件中使用

使用store.dispatch()传入action对象

使用store.getStore()获取state对象数据 

import store from "./store"

function App() {
  return (
    <>
      <div>{store.getState().num}</div>
      <button onClick={() => { store.dispatch({ type: 'increment', num: 5 }) }}>+5</button>
      <button onClick={() => { store.dispatch({ type: 'decrement', num: 5 }) }}>-5</button>
    </>
  )
}

(3)拆分组件

一般会将上面的部分拆分为四个文件:

  • index.js  入口文件
  • reducer.js  reducer函数
  • actionCeactors.js  存放action函数
  • constant,js  存放type常量
// index.js
import { createStore } from "redux";
import reducer from './reducer.js'

// createStore的写法已被取代
const store = createStore(reducer)

// 订阅state的变化
store.subscribe(() => {
    console.log("state发生了改变....", store.getState().num);
});

export default store

// reducer.js
import { INCREMENT, DECREMENT } from "./constant";

const initialState = {
    num:0
}

const reducer = (state = initialState, action) => {
    console.log('state', state, 'action', action);
    switch (action.type) {
        case INCREMENT: {
            return {
                ...state,
                num: state.num + action.num
            }
        }
        case DECREMENT: {
            return {
                ...state,
                num: state.num - action.num
            }
        }
        default: {
            return state
        }
    }
}

export default reducer

// actionCreator.js
import { INCREMENT, DECREMENT } from "./constant";

export const incrementAction = (num)=>{
    return {
        type: INCREMENT,
        num
    }
}
export const decrementAction = (num)=>{
    return {
        type: DECREMENT,
        num
    }
}

// constant.js
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'

在组件里使用:

import store from "./store"
import { incrementAction, decrementAction } from "./store/actionCreators"

function App() {
  return (
    <>
      <div>{store.getState().num}</div>
      <button onClick={() => { store.dispatch(incrementAction(5)) }}>+5</button>
      <button onClick={() => { store.dispatch(decrementAction(5)) }}>-5</button>
    </>
  )
}

2.reduxjs/toolkit的使用

Quick Start | React Redux

在react开发中,默认toolkit和react-redux一起使用  

将 Redux Toolkit 和 React Redux 包添加到项目中

npm install @reduxjs/toolkit react-redux

还是以计数器为例: 

(1)创建store

在src/app/store.js文件中创建store 

import { configureStore } from "@reduxjs/toolkit";

export default configureStore({
    reducer:{
        
    },
})

在main.jsx中:

使用react-redux的Provider组件包裹整个App组件,将store以prop形式传递

这里就有useContext那味儿了

import store from './app/store.js'
import { Provider } from 'react-redux'

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
)

 

(2)创建reducer

在src/features/counterSlice.js中创建计数器组件的reducer

可以把counterSlice理解为counter组件store的切片,在里面把不同的action进行切片处理

通过createSlice()创建counter组件的reducer 

// 存放counter组件的reducer

import { createSlice } from "@reduxjs/toolkit";
// 初始化数据
const initialState = {
    num:0
}

export const counterSlice = createSlice({
    name:'counter',
    initialState:initialState,
    reducers:{
        increment:(state, action)=>{
            console.log(action);
            // createSlice API内置了Immer,因此可以直接操作state
            state.num = state.num + action.payload
        },
        decrement:(state, action)=>{
            state.num = state.num - action.payload
        },
    }
})

console.log(counterSlice.actions);

// 获取createSlice()的action
export const { increment, decrement } = counterSlice.actions
// redux使用createReducer()创建了这个reducer
export default counterSlice.reducer

注意:createSlice API内置了Immer,因此在reducers里可以直接修改state

reducer里函数的两个形参分别是state、action,action分为type和payload

createSlice()返回的实际是一个对象:

counterSlice.actions:

 

counterSlice.reducer是一个函数,由buildReducer方法创建

 

再把buildReducer掏出来就会发现,redux的reducer就是用createReducer创建的

再掏。。有点看不懂了,但是都是蛮眼熟的代码,额算了 

(3)将reducer引入到store中,在组件中使用

// 将counterReducer添加到store里
import counterReducer from '../features/counter/counterSlice'

export default configureStore({
    reducer:{
        counter: counterReducer,
    },
})

在组件里使用:

通过引入useSelector()获取state数据,useDispatch()来获取dispatch函数,再使用在counterSlice导出的action传参,传入的数据就会变为reducers的函数里的action.payload

import { useSelector, useDispatch} from 'react-redux'
import { increment,decrement } from './features/counter/counterSlice'
function App() {
  const num = useSelector((state)=>state.counter.num)
  const dispatch = useDispatch()
  return (
    <>
      <div>{num}</div>
      <button onClick={() => { dispatch(increment(5)) }}>+5</button>
      <button onClick={() => { dispatch(decrement(5)) }}>-5</button>
    </>
  )
}

这样就能实现state响应式啦,还不需要在每个组件里订阅,蛮方便蛮方便

(四)总结

本篇文章介绍了useReducer,useContext以及rudux的简单应用

  1. useReducer Hook,用于集中管理state,可以说是小型的redux,但是只能在组件内使用;
  2. useContext Hook,可以实现爷孙组件的传递,无论有多深;
  3. redux,适配react的状态管理库,原来的写法过于复杂,toolkit和react-redux结合的写法更简单一些,但是由于我对react的组件化结构还不太了解,目前只能算是初步学习到了redux;

学习的时候一直纠结useReducer和redux的区别在哪,经查阅后发现可以用useReducer和useContext写一个简单的redux,下一篇就写这个,来更好的体会redux和react的特性

  • 10
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值