React Router 与 Valtio
Router
# 安装与使用
React-router分为若干个版本:
react-router: 路由的核心库,提供了很多的组件、钩子。
[适用于web] react-router-dom: 包含react-router所有内容,并添加一些专门用于 DOM 的组件,例如 <BrowserRouter>
等。
[适用于react-native] react-router-native: 包括react-router所有内容,并添加一些专门用于ReactNative的API,例如:<NativeRouter>
等。
因此:
web端安装: pnpm i react-router-dom@6
native端安装: pnpm i react-router-native@6
# 开始使用
-
首先,在根组件
index.tsx / main.tsx
中引入React-router
import { BrowserRouter } from "react-router-dom"; ... //路由前缀 const baseUrl = import.meta.env.VITE_APP_ROUTER_PREFIX || ""; root.render( <StrictMode> <BrowserRouter basename={baseUrl}> //包裹APP组件 <App /> </BrowserRouter> </StrictMode> );
BrowserRouter
是最常用的路由方式,即浏览器路由。官方文档也建议将BrowserRouter
组件用于 Web 应用程序。除了这种方式,React Router 还支持其他几种路由方式:-
HashRouter
:在路径前加入#
成为一个哈希值,Hash 模式的好处是不会因为刷新页面而找不到对应路径; -
MemoryRouter
:不存储 history,路由过程保存在内存中,适用于 React Native 这种非浏览器环境; -
NativeRouter
:配合 React Native 使用,多用于移动端; -
StaticRouter
:主要用于服务端渲染时。
-
-
创建导航链接,导入
<NavLink> / <Link>
组件用于创建导航链接.<NavLink> / <Link>
组件类似于HTML中的a组件.类似于Vue中的<router-link>
组件.... <nav> //HTML中的原生组件,用来表示链接 <NavLink to="">首页</NavLink> <NavLink to="product">产品</NavLink> <NavLink to="about">关于</NavLink> </nav> ...
其中,
<NavLink>
是React-Route封装的一个特殊的Link. 它知道自己是否处于 "激活"、"待定 "或 "过渡 "状态。它提供了一个 "过渡" 值,可让您对视图转换进行更精细的控制. 这个控制点体现在(
<Link
没有这个属性):- className/style
:
import { NavLink } from "react-router-dom"; <NavLink to="/messages" className={({ isActive, isPending, isTransitioning }) => isPending ? "pending" : isActive ? "active" : "" } > Messages </NavLink>;
<NavLink>
的className/style的作用于普通DOM的作用相同. 不同的是React-Router的<NavLink>
可以向className/style里面传入函数,根据链接的活动和待定状态自定义应用的 className/style。
当然,除了className/style之外,还可以在
<NavLink>
里内嵌组件/DOM元素:- <children>
:
<NavLink to="/tasks"> {({ isActive, isPending }) => ( <span className={isActive ? "active" : ""}>Tasks</span> )} </NavLink>
-
然后,我们需要在需要展示路由的地方放上
<Routes> </Routes>
:我们需要在
Routes
组件中使用Route
组件来定义所有路由。该组件接受两个props
:path
:页面 URL 应导航到的路径,类似于 NavLink 组件的 to;element
:页面导航到该路由时加载的元素。
Route
组件用于将应用的位置映射到不同的 React 组件。import { Routes } from "react-router-dom"; export default function App() { return ( <div className="App"> <header> <h1>Route Page: ....</h1> <Routes> { /*此为重定向 */} <Route path="" element={<> <Navigate to="/product"replace />} </>}/> <Route path="product" element={<Product />} /> <Route path="about" element={<About />} /> { /*如果前面的都不匹配,则跳转Error */} <Route path="*" element={<Error />} /> </Routes> </div> ); }
-
嵌套路由
嵌套路由仍然写在原先的Routes里面,并在Route里面嵌套一个Route.
... <NavLink to="/production/buy">fakeChild</NavLink> <Routes> <Route path="main" element={<h2>this is /main</h2>} /> ... <Route path="production" element={<FakeChild />}> <Route path="buy" element={<h2>付款链接</h2>} /> </Route> </Routes> ...
其中,buy就是嵌套路由. 在 FakeChild 组件中,在需要显示
付款链接
的地方 写上<Outlet />
即可.
# 编程式导航
React Router 提供了两种不同的编程式导航方式:
- 声明式导航组件:
<Navigate>
组件 - 命令式导航方法:
useNavigate
Hook
声明式编程和命令式编程是两种不同的编程范式,它们的主要区别在于描述问题的方式和关注点不同:
声明式编程:
- 定义:声明式编程更侧重于“说什么(what),而非怎么做(how)”。程序员主要描述他们想要的结果或目标状态,而不是具体步骤。
- 特点:
- 程序员声明逻辑关系、约束条件或者数据流动规则。
- 编程语言通常会隐藏底层实现细节,抽象层次较高。
- 示例:SQL查询语句(描述要获取的数据而非如何获取)、函数式编程中的高阶函数、React的JSX语法(描述UI应呈现的状态而非DOM操作过程)等。
命令式编程:
- 定义:命令式编程强调“怎么做”,程序员需要明确指定计算机执行的一系列操作步骤,以达到最终结果。
- 特点:
- 程序由一系列指令组成,每个指令改变程序的状态。
- 控制流通过分支(if-else, switch-case)和循环(for, while)等方式精确控制计算过程。
- 示例:C语言、Java、Python等大多数传统的通用编程语言都属于命令式编程,其中包含对变量赋值、调用函数以及控制流程的操作。
总结: 在声明式编程中,开发者更多地表达逻辑和关系,让系统自行解决如何执行。而在命令式编程中,开发者必须手动安排每一个动作序列来完成任务。声明式编程通常更容易理解和维护,因为它与业务逻辑更加贴近;而命令式编程提供了对底层控制的直接性,有时可以提供更强的灵活性和性能优化空间。
-
<Navigate>
<Navigate>
组件是一种声明式的导航方式。在Navigate
组件中通过to
props 来指定要跳转的路径:<Route path="*" element={<Navigate to="/" />} />
这样,当在浏览器地址栏输入一个未定义的路由时,就会要转到首页。 [相当于重定向]
-
useNavigate
useNavigate
Hook是一种命令式导航方式。传递给它需要跳转的路由即可。export default function App() { const navigate = useNavigate(); const toBuy = () => { navigate('/production/buy'); }; ... }
# 通过路由传递状态
在 react-router-dom 中可以通过以下三种方式来传递状态:
-
使用
Link/NavLink
组件使用 Link 组件通过
state
props 来将数据进行传递:<Link to="/" state={"From Product"}> 返回 </Link>
那该如何获取传递出来的数据呢?可以在接收信息的页面中使用一个名为
useLocation
的钩子来获取数据:import { useLocation } from "react-router-dom"; export default function Settings() { let location = useLocation(); return ( <p>{location.state}</p> ); }
-
使用
Navigate
组件与Link相同.
-
使用
useNavigate
钩子实际上,
navigate()
函数接受两个参数,第一个参数就是跳转的路径,第二个参数是包含状态的对象。可以借助useNavigate
Hook 来实现状态传递.export default function App() { const navigate = useNavigate(); const toBuy = () => { navigate('/production/buy',{state:{who:'are you ok?'}}); }; ... }
# 查询路由参数
React Router 使用 URLSearchParams
API 来处理查询字符串.
React Router 提供了一个自定义的 useSearchParams
Hook,它是基于 URLSearchParams
进行的封装。
const [searchParams, setSearchParams] = useSearchParams(); //内置许多方法 searchParams.getAll(); const f = searchParams.get('f')
如果需要更新查询字符串,可以使用 setSearchParams
,向它传递一个对象,该对象的key/value
对将作为 &key=value
添加到 url.
# 使用 useRoutes 写路由
import { useRoutes } from "react-router-dom"; const routes = useRoutes([ { path: "/", element: <Home /> }, { path: "/invoices", element: <Invoices />, children: [ { path: ":id", element: <Invoice /> }, { path: "/pending", element: <Pending /> }, { path: "/complete", element: <Complete /> }, ], }, ]); export default function App() { return ( <div> <Navbar /> {routes} </div> ); }
Valtio
轻量级的响应式状态管理库.
# 安装
pnpm i valtio
# 创建Store文件
首先创建一个store文件夹,然后创建一个store文件. (类似于Pinia)
Valtio是通过Proxy(代理)而实现的. (类似于Pinia)
构建:
import { proxy } from 'valtio' const countStore = proxy<{count:number}>({ count:0, });
访问: 快照是只读的
store = useSnapshot(countStore)表示当前组件已经有了对countStore的响应式,更改countStore内容会触发组件更新.
import { useSnapshot } from 'valtio'; import { countStore } from './store/countStore'; const Count = () => { const store = useSnapshot(countStore); return ( <div> <h1>count is : {store.count}</h1> </div> ); }; export default Count;
修改:
import { countStore } from './store/countStore'; const store = useSnapshot(countStore); ... <button className="border" onClick={() => countStore.count++}> count ++ </button>
注意: 修改的时候不是用的store,因为store(useSnapshot)是只读属性,修改的时候直接用countStore进行修改.
计算属性:
import { proxy } from 'valtio'; export const countStore = proxy<{ count: number; double: number }>({ count: 0, get double() { return this.count * 2; }, set double(newVal) { this.count = newVal / 2; }, });
ref: 使用ref包裹的对象/对象属性将不会被设置为响应式的.
监听/订阅:
// Subscribe to all changes to the state proxy (and its child proxies) const unsubscribe = subscribe(state, () => console.log('state has changed to', state), ) // Unsubscribe by calling the result unsubscribe()
还可以订阅状态的一部分(只能是对象)。
const state = proxy({ obj: { foo: 'bar' }, arr: ['hello'] }) subscribe(state.obj, () => console.log('state.obj has changed to', state.obj)) state.obj.foo = 'baz' subscribe(state.arr, () => console.log('state.arr has changed to', state.arr)) state.arr.push('world')
综上,由于对象在Proxy中具有更大潜力,因此,最好在创建的时候直接创建一个对象.
//原来 export const countStore = proxy<{ count: number; double: number }>({ count: 0, }); //现在 export const countStore = proxy<{ count:Record<string,any> }>({ count: { value:0, } });