重生之我又来学React了Day05 -- React-Redux/React-Router

116 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第5天,点击查看活动详情

image.png

Redux

学React-redux之前我们先了解一下Redux,Redux 是 JavaScript 应用的一个全局状态管理器。它既可以和中React中使用也可以在Vue中使用。React-redux就是React和Redux的集成,使开发者能够更方便的在React中使用Redux。

中文文档:cn.redux.js.org/api/creates…

npm install redux
// store.js
import { createStore } from "redux";
//  创建一个reducer
function counterReducer(state = { value: 3 }, action) {
  switch (action.type) {
    case "counter/incremented":
      return { value: state.value + action.payload };
    case "counter/decremented":
      return { value: state.value - action.payload };
    default:
      return state;
  }
}

// 通过createStore传入一个reducer 创建一个store
const store = createStore(counterReducer);

export default store;
// App.js
import store from "./store";
import { useState } from "react";

export default function App() {
  // 获取store的初始值
  let [age, setAge] = useState(store.getState().value);
  // 监听store的修改
  store.subscribe(() => setAge(store.getState().value));
  // 触发store的action 来修改store里的值
  function clickHandler(type, payload) {
    store.dispatch({ type: `counter/${type}`, payload });
  }
  return (
    <div className="App">
      {age}
      <button onClick={() => clickHandler("incremented", 2)}>
        触发+2 action
      </button>
      <button onClick={() => clickHandler("decremented", 1)}>
        触发-1 action
      </button>
    </div>
  );
}

Redux-Toolkit

npm install @reduxjs/toolkit

一个Redux相关的第三方库,从字面上理解就是Redux的工具方法。

我们使用Redux-Toolkit开发可以简化许多Redux的使用场景,提升开发效率。

// store.js
import { createSlice, configureStore } from '@reduxjs/toolkit'
// 创建一个counterSlice 可以想象为一个命名空间
const counterSlice = createSlice({
  name: 'counter',
  initialState: {
    value: 0
  },
  reducers: {
    incremented: (state, action) => {
      // Redux Toolkit 允许在 reducers 中编写 "mutating" 逻辑。
      // 它实际上并没有改变 state,因为使用的是 Immer 库,检测到“草稿 state”的变化并产生一个全新的
      // 基于这些更改的不可变的 state。
      state.value += action.payload
    },
    decremented: (state, action) => {
      state.value -= action.payload
    }
  }
})

export const { incremented, decremented } = counterSlice.actions

// 把counterSlice的reducer传给了叫reducer的子store
const store = configureStore({
  reducer: counterSlice.reducer
})
export default store
// index.js
import store from "./store";
import { useState } from "react";
import { incremented, decremented } from './store';

export default function App() {
  // 获取store的初始值
  let [age, setAge] = useState(store.getState().value);
  // 监听store的修改
  store.subscribe(() => setAge(store.getState().value));
  return (
    <div className="App">
      {age}
      {/* 直接使用incremented和decremented */}
      <button onClick={() => incremented(2)}>
        触发+2 action
      </button>
      <button onClick={() => decremented(1)}>
        触发-1 action
      </button>
    </div>
  );
}

Redux-thunk

到目前为止,我们使用dispatch执行action,都是同步调用的,那么如何异步调用呢?这个时候就需要使用Redux-thunk了。利用Redux的middleware机制,当发起异步请求的时候,先在middleware(thunk)中处理完数据再disaptch相应的action来修改steta里面的数据。

// store.js
export const store =  configureStore({
  reducer: {
    user: usersSlice.reducer, // 这里我们把user相关的store的值放到userSlice里
  },
  // 通过redux中间件来引入thunk
  middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(thunk)
})
// usersSlice.js

import { registerFetch } from '../api/user' // registerFetch是一个ajax请求
// 创建thunk 在后面的extraReducers的[registerFetchThunk.fulfilled]中会接受到这个值
const registerFetchThunk = createAsyncThunk(
  'users',
  async (payload) => {
    const response = await registerFetch(payload)
    return response
  }
)

const initialState = {
    userInfo: {},
    loading: false
}

// 创建slice
const usersSlice = createSlice({
  name: 'users',
  initialState,
  reducers: {},
  // 监听action的状态(pending, fulfilled, rejected),根据状态来修改state里面的值
  extraReducers(build) {
      build
      .addCase(registerFetchThunk.pending, (state, action) => {
        state.loading = true;
      })
      .addCase(registerFetchThunk.fulfilled, (state, action) => {
        state.loading = false;
        state.userInfo = action.payload; // 获取thunk返回的值
      })
      .addCase(registerFetchThunk.rejected, (state, action) => {
        state.loading = false;
      })
  },
})

// 抛出usersSlice以传入store中,抛出registerFetchThunk给组件调用
export { usersSlice, registerFetchThunk };

⚠️在组件中通过dispatch调用thunk方法的时,因为thunk在内部对所有错误进行了处理,所以如果我们在dispatch thunk方法的时候直接使用try catch是获取不到错误的,这时我们加一个unwrap()方法即可。

try {
    dispatch(registerFetchThunk({username, password})).unwrap();
} catch(err) {
    console.log('error', err)
}

React-Redux

终于来到了React-Redux,那么到底React-Redux是什么?跟Redux的又有什么关系?根据上面对于Redux的介绍,我们知道要使用Redux的话就需要引入store,拿到store里面的state,然后可以通过store里面的方法去操作store里面的state。 那如果我很多个组件都需要使用store,那我的每一个组件都要去引入一次store吗?明显不可能这样操作。

之前我们有接触到过context,可以通过createContext把store从父组件传进所有的子组件,所以React-Redux对Redux和React做了集成,实现了上面所描述的操作(不仅仅只实现了这一个操作),简化了Redux在React中的使用,提高了开发效率。

import React from 'react';  
import ReactDOM from 'react-dom/client';
import store from './app/store';  
// 引入Provider组件
import { Provider } from 'react-redux';

const root = ReactDOM.createRoot(document.getElementById('root'));  
// 通过Provider组件传入store
root.render(  
    <Provider store={store}>  
        <App />  
    </Provider>  
);

同时React-Redux封装了一些获取state和操作state的Hook方法。

import { registerFetchThunk } from '../../store/user';
import { useSelector, useDispatch } from 'react-redux'
// 获取store里面的值
const user = useSelector((state) => state.user.userInfo)
// 需要使用dispatch来调用action的方法
const dispatch = useDispatch();

// 调用registerFetchThunk 这个thunk方法
dispatch(registerFetchThunk()).then(res=> {
      console.log('res', res)
})

image.png

React-Router (V6)

React Router是为React设计的一个功能齐全的可以用在客户端也可以用在服务端的路由库。

npm install react-router-dom
// index.js
import { createRoot } from "react-dom/client";
import List from "./routes/list";
import User from "./routes/user";
import { BrowserRouter, Routes, Route } from "react-router-dom";

import App from "./App";
const rootElement = document.getElementById("root");
const root = createRoot(rootElement);

root.render(
  <BrowserRouter>
    <Routes>
      <Route path="/" element={<App />} />
      <Route path="list" element={<List />} />
      <Route path="user" element={<User />} />
    </Routes>
  </BrowserRouter>
);
// App.js
import "./styles.css";
// 引入 Link 组件
import { Link } from "react-router-dom";

export default function App() {
  return (
    <div className="App">
     {/* to属性就是需要跳转到的路由地址 */}
      <Link to="/list">List</Link> | <Link to="/user">User</Link>
    </div>
  );
}
// list.js
export default function () {
  return <div>list page</div>;
}

// user.js
export default function () {
  return <div>user page</div>;
}

image.png

点击List

image.png

点击User

image.png

那么如果我们需要页面拥有共同的layout该如何操作呢? 这个时候就需要使用嵌套路由了。

// 修改index.js的render内容
root.render(
  <BrowserRouter>
    <Routes>
     {/* 将list和user作为app的子route */}
      <Route path="/" element={<App />}>
        <Route path="list" element={<List />} />
        <Route path="user" element={<User />} />
      </Route>
    </Routes>
  </BrowserRouter>
);

同时App.js也要做修改

// 引入Outlet组件
import { Link, Outlet } from "react-router-dom";

export default function App() {
  return (
    <div className="App">
      <Link to="/list">List</Link> | <Link to="/user">User</Link>
       {/* 子路由的内容将被渲染到Outlet这里 */}
      <Outlet />
    </div>
  );
}

如果用户跳转到一个不存在的路由地址,页面又该如何展示呢?这个时候添加一个“无匹配”路由就好了。

root.render(
  <BrowserRouter>
    <Routes>
      <Route path="/" element={<App />}>
        <Route path="list" element={<List />} />
        <Route path="user" element={<User />} />
        {/* 其他路由都不匹配的时候就会走到这个路由 */}
        <Route path="*" element={<p>页面不存在!</p>} />
      </Route>
    </Routes>
  </BrowserRouter>
);

image.png

NavLink

当我们点击Link的时候,想要给它一个激活的样式,该如何操作呢?这个时候我们就可以把Link组件换成NavLink组件。NavLink组件可以接受到当前路由是否是自己“代表”的理由,如果是则可以给它自己设置一个激活的样式。

// App.js
import "./styles.css";
import { NavLink, Outlet } from "react-router-dom";

export default function App() {
  return (
    <div className="App">
    {/* 当路由是list 该组件激活状态为红色*/}
      <NavLink
        to="/list"
        style={({ isActive }) => {
          return {
            color: isActive ? "red" : ""
          };
        }}
      >
        List
      </NavLink>{" "}
      | {/* 当路由是user 该组件激活状态为红色*/}
      <NavLink
        to="/user"
        style={({ isActive }) => {
          return {
            color: isActive ? "green" : ""
          };
        }}
      >
        User
      </NavLink>
      <Outlet />
    </div>
  );
}

还有一些与React-Router相关的Hooks方法

之前React Hook章节中,我们有接触到useParams(),useNavigate(),useSearchParams()这三个Hook,在此不再赘述

useLocation

通过这个Hook 我们可以获取到当前的路由信息

const location = useLocation()

image.png