zustand-router
路由导航
声明式导航
tsx
import { Link } from "react-router";
export function Login() {
return (
<div>
Login
<Link to="/article">Link to article</Link>
</div>
);
}
编程式导航 (hook: useNavigate)
tsx
export function Login() {
const navigate = useNavigate();
return (
<div>
Login
<button onClick={() => navigate("/article")}>Navigate to article</button>
</div>
);
}
路由导航传参
SearchParams 传参 (hook: useSearchParams)
tsx
// src/page/Login.tsx
navigate("/article?name=whoami&age=23");
// src/page/Article.tsx
const [params, setParams] = useSearchParams();
const name = params.get("name");
const age = params.get("age");
Params 传参 (hook: useParams)
tsx
// src/page/Login.tsx
navigate("/article?name=whoami&age=23");
// src/page/Article.tsx
const params = useParams(); // params 只读
console.log(params.name, params.age);
嵌套路由, 默认二级路由
tsx
export const router = createBrowserRouter([
{
path: "/layout",
element: <Layout />,
children: [
{
index: true, // 默认二级路由 (默认渲染的二级路由组件)
// 等价于设置 path: '',
element: <About />,
},
{
path: "about", // /layout/about
element: <About />,
},
{
path: "/layout/board", // board
element: <Board />,
},
],
},
]);
tsx
export function Layout() {
return (
<div>
Layout 一级路由
{/* 等价于 to="about" */}
<Link to="/layout/about">About</Link>
{/* 等价于 to="/layout/board" */}
<Link to="board">Board</Link>
{/* Outlet: 渲染父路由的匹配子路由, 如果没有匹配的子路由, 则不渲染
类比 Vue 的 <RouterView> */}
<Outlet />
</div>
);
}
404 路由配置
tsx
export const router = createBrowserRouter([
{
path: "*",
element: <NotFound />,
},
]);
路由模式
- history 模式:
createBrowserRouter()
- hash 模式
createHashRouter()
路由模式 | URL | 原理 | 方法 |
---|---|---|---|
history | /login | history 对象的 pushState 方法 | createBrowserRouter() |
hash | /#/login | 监听 hashchange 事件 | createHashRouter() |
bash
pnpm install zustand -D
zustand 创建并使用 store
ts
interface Store {
cnt: number;
addCnt: () => void;
resetCnt: () => void;
}
export const useCntAndListStore = create<Store>((set) => {
return {
cnt: 0,
addCnt: () => {
set((state: Store) => ({ cnt: state.cnt + 1 }));
},
resetCnt: () => {
set({ cnt: 0 });
},
};
});
tsx
function App() {
const { cnt, addCnt, resetCnt } = useCntAndListStore();
return (
<>
<div>
<div>cnt: {cnt}</div>
<button type="button" onClick={addCnt} className="rounded border-1">
addCnt
</button>
<button type="button" onClick={resetCnt} className="rounded border-1">
resetCnt
</button>
</div>
</>
);
}
异步 zustand
ts
interface Store {
nameList: { id: number; cnName: string }[];
fetchList: () => Promise<void>;
}
export const useCntAndListStore = create<Store>((set) => {
return {
nameList: [],
fetchList: async () => {
const res = await fetch("/api/list").then((res) => res.json());
const { code, message, data } = res;
console.log(code, message);
// 修改 zustand 状态必须调用 set 方法
set({ nameList: data.list });
},
};
});
tsx
function App() {
const { nameList, fetchList } = useCntAndListStore();
// fetchList()
useEffect(() => {
fetchList(); // effect: React.EffectCallback
fetchList2(); // effect: React.EffectCallback
}, [fetchList, fetchList2]); // deps?: React.DependencyList
return (
<>
<div>nameList.length: {nameList.length}</div>
<ul className="flex justify-between">
{nameList.map((item) => (
<li key={item.id}>{item.cnName}</li>
))}
</ul>
<button type="button" onClick={fetchList}>
fetchList
</button>
</>
);
}
store 切片
ts
import { StateCreator } from "zustand";
export interface CntStore {
cnt: number;
addCnt: () => void;
resetCnt: () => void;
}
export const createCntSlice: StateCreator<CntStore> = (set) => {
return {
cnt: 0,
addCnt: () => {
set((state: CntStore) => ({ cnt: state.cnt + 1 }));
},
resetCnt: () => {
set({ cnt: 0 });
},
};
};
ts
import { StateCreator } from "zustand";
export interface ListStore {
nameList: { id: number; cnName: string }[];
fetchList: () => Promise<void>;
}
export const createListSlice: StateCreator<ListStore> = (set) => {
return {
nameList: [],
fetchList: async () => {
const res = await fetch("/api/list").then((res) => res.json());
const { code, message, data } = res;
console.log(code, message);
// 修改 zustand 状态必须调用 set 方法
set({ nameList: data.list });
},
};
};
tsx
import { create } from "zustand";
import { type CntStore, createCntSlice } from "./cnt_slice";
import { createListSlice, type ListStore } from "./list_slice";
export const useCntAndListStore2 = create<CntStore & ListStore>((...args) => {
console.log(args.length);
return {
...createCntSlice(...args),
...createListSlice(...args),
};
});
tsx
function App() {
// store 实例 1, 未切片
const { cnt, addCnt, resetCnt, nameList, fetchList } = useCntAndListStore();
// store 实例 2, 切片
const {
cnt: cnt2,
addCnt: addCnt2,
resetCnt: resetCnt2,
nameList: nameList2,
fetchList: fetchList2,
} = useCntAndListStore2();
// fetchList()
useEffect(() => {
fetchList(); // effect: React.EffectCallback
fetchList2(); // effect: React.EffectCallback
}, [fetchList, fetchList2]); // deps?: React.DependencyList
return (
<>
<div>
<div>cnt: {cnt}</div>
<button type="button" onClick={addCnt} className="rounded border-1">
addCnt
</button>
<button type="button" onClick={resetCnt} className="rounded border-1">
resetCnt
</button>
</div>
<div>nameList.length: {nameList.length}</div>
<ul className="flex justify-between">
{nameList.map((item) => (
<li key={item.id}>{item.cnName}</li>
))}
</ul>
<button type="button" onClick={fetchList}>
fetchList
</button>
<hr />
<div>
<div>cnt: {cnt2}</div>
<button type="button" onClick={addCnt2} className="rounded border-1">
addCnt2
</button>
<button type="button" onClick={resetCnt2} className="rounded border-1">
resetCnt2
</button>
</div>
<div>nameList2.length: {nameList2.length}</div>
<ul className="flex justify-between">
{nameList2.map((item) => (
<li key={item.id}>{item.cnName}</li>
))}
</ul>
<button type="button" onClick={fetchList2}>
fetchList2
</button>
</>
);
}
export default App;