开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第5天,点击查看活动详情
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)
})
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>;
}
点击List
点击User
那么如果我们需要页面拥有共同的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>
);
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()