react-router
React-Router 是一个用于在 React 应用中实现页面导航和路由管理的库。它提供了一种方式来创建单页应用(Single-Page Application,SPA),其中页面的切换是在客户端进行的,而不需要每次跳转都向服务器请求新的页面。
使用 React-Router,你可以定义应用的路由规则,并且根据这些规则渲染不同的组件。你可以将路由映射到特定的URL,当用户在浏览器中访问这些 URL 时,React-Router 将会根据配置的规则找到对应的组件并将其渲染到页面上。
更多精彩内容,请微信搜索“前端爱好者
“, 戳我 查看 。‘
React-Router 提供了一些核心组件,包括 Router、Route、Link 和 Switch。
-
Router:Router 是整个应用的容器组件,它负责管理应用的路由信息。React-Router 提供了多个不同的 Router 组件,如 BrowserRouter、HashRouter 和 MemoryRouter,你可以根据需要选择适合的 Router 组件。
-
Route:Route 组件用于定义路由规则和要渲染的组件。你可以通过 path 属性指定匹配的 URL 路径,通过 component 属性指定要渲染的组件。当 URL 匹配到对应的路由规则时,Route 会渲染相应的组件。
-
Link:Link 组件用于创建导航链接,它会生成一个带有正确 URL 的可点击元素,使用户可以导航到其他路由。使用 Link 组件,可以避免刷新整个页面,而只更新需要变化的部分。
-
Switch:Switch 组件用于包裹多个路由规则,它只会渲染第一个匹配成功的路由规则对应的组件。这对于避免多个路由同时匹配的情况非常有用,确保只有一个组件被渲染。
除了以上核心组件,React-Router 还提供了其他一些实用的组件和功能,如 Redirect 组件用于重定向到其他路由,嵌套路由用于构建复杂的页面结构,动态路由参数用于处理可变的 URL 参数等等。
以下是一个简单的使用 React-Router 的示例:
import { BrowserRouter as Router, Route, Link, Switch } from 'react-router-dom';
const Home = () => <h1>Home Page</h1>;
const About = () => <h1>About Page</h1>;
const Contact = () => <h1>Contact Page</h1>;
const App = () => (
<Router>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/contact">Contact</Link>
</li>
</ul>
</nav>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/contact" component={Contact} />
</Switch>
</Router>
);
export default App;
在上述示例中,我们使用 BrowserRouter 作为 Router 组件,并定义了三个路由规则:根路径对应 Home 组件,“/about” 对应 About 组件,“/contact” 对应 Contact 组件。导航链接使用 Link 组件来创建,点击链接时,React-Router 会根据规则渲染相应的组件。
这只是 React-Router 的基本用法,它还提供了更多高级功能,如嵌套路由、动态路由、路由参数等,可以根据实际需求进行进一步学习和应用。
React-Router V5 VS React-Router V6
React-Router 是一个流行的用于在 React 应用程序中进行导航和路由管理的库。目前最新版本是 React-Router V6。
React-Router V6 相比于 V5 有一些重要的改进和变化。以下是一些 V6 的主要特点:
-
路由声明方式改变: 在 V6 中,路由声明方式发生了重大变化。不再使用
<Route>
组件,而是改为使用useRoutes()
和Outlet
组件。这种更加函数式的声明方式使得在组件内部定义路由成为可能。 -
动态路由匹配: V6 引入了新的
path-to-regexp
匹配引擎,它可以在运行时动态生成路由匹配规则。这意味着你可以在路由声明中使用变量和占位符,使得路由配置更加灵活和动态。 -
路由导航: 在 V6 中,路由导航发生了一些改变。通过
useNavigate
钩子函数,在组件内部可以轻松地进行编程式导航。此外,针对导航的声明式组件(如<Link>
、<NavLink>
)也有所变化。 -
嵌套路由: V6 改进了嵌套路由的处理方式。现在可以在一个组件中定义子组件的路由,这样可以更好地组织和管理复杂的应用程序结构。
-
重定向: V6 引入了
<React.Fragment>
组件,它可以用于在路由配置中定义重定向。这使得管理重定向更加清晰和灵活。
总的来说,React-Router V6 提供了更加灵活、简洁和直观的 API,使开发者能够更好地控制应用程序的导航和路由。然而,由于它与 V5 在很多方面有较大的不兼容性,如果你打算升级你的项目到 V6,请确保仔细阅读官方文档并进行适当的迁移工作。
React-Router 的实现原理是什么?
客户端路由实现的思想:
基于 hash 的路由:通过监听hashchange 事件,感知 hash 的变化改变 hash 可以直接通过 location.hash=xxx
基于 H5 history 路由:
改变 url 可以通过 history.pushState 和 resplaceState 等,会将 URL 压入堆栈,同时能够应用 history.go() 等 API监听 url 的变化可以通过自定义事件触发实现
react-router 实现的思想:
基于 history 库来实现上述不同的客户端路由实现思想,并且能够保存历史记录等,磨平浏览器差异,上层无感知
通过维护的列表,在每次 URL 发生变化的回收,通过配置的 路由路径,匹配到对应的 Component,并且 render
react-router 里的 Link 标签和 a 标签的区别
在 React-Router 中,Link
组件和 <a>
标签都用于创建导航链接,但它们在实际使用中有一些重要的区别。
-
路由导航:
Link
组件通过 React-Router 提供的路由机制进行导航,而<a>
标签则是浏览器默认的页面跳转行为。使用Link
组件时,React-Router 会处理导航请求,只更新需要更新的组件,而不刷新整个页面。这样可以提高性能并保持页面状态。 -
嵌套路由:
Link
组件在嵌套路由中的表现更好。它会自动处理父级路由和子级路由之间的关系,确保正确匹配和渲染嵌套的路由层次结构。当你使用<a>
标签时,可能需要手动处理嵌套路由的逻辑。 -
阻止默认行为:
Link
组件会阻止默认的点击事件行为,从而防止页面刷新。相反,<a>
标签会触发浏览器的默认跳转行为,导致整个页面重新加载。 -
活动状态样式:
Link
组件可以自动管理活动状态的样式。当链接与当前路由匹配时,Link
组件会自动添加活动状态的类名,使得你可以方便地为活动链接定义样式。而<a>
标签不提供自动管理活动状态的功能,你需要手动添加类名来表示当前活动的链接。
总结:Link
组件是 React-Router 提供的专门用于导航的组件,它通过 React-Router 的路由机制进行导航,提供更好的性能和用户体验。相较之下,<a>
标签则是普通的 HTML 元素,用于普通网页的跳转。在使用 React-Router 构建导航时,推荐使用 Link
组件来确保正确的导航行为和优化性能。
a 标签默认事件禁掉之后做了什么才实现了跳转?
react-router 声明式路由和编程式路由
在 React Router 中,有两种主要的路由方式:声明式路由和编程式路由。
它们在实现上有一些区别和不同的应用场景。
-
声明式路由:
- 声明式路由是通过配置路由规则来定义路径和组件之间的映射关系。
- 使用 JSX 或者路由配置对象的形式定义路由规则,使代码更具可读性。
- 在 React Router v6 中,我们使用
<Routes>
和<Route>
组件来创建声明式的路由表。 - 优点:
- 易于阅读和理解,将路由规则集中于一个地方,方便维护和扩展。
- 结构清晰,代码组织得当。
- 示例代码(React Router v6):
import { BrowserRouter, Routes, Route } from 'react-router-dom'; function App() { return ( <BrowserRouter> <Routes> <Route path="/" element={<Home />} /> <Route path="/about" element={<About />} /> </Routes> </BrowserRouter> ); }
-
编程式路由:
-
编程式路由是通过 JavaScript 代码来控制路由的跳转和导航。
-
通过调用路由 API,如
history
对象的方法或钩子函数返回的导航函数,来进行页面的导航。 -
可以根据业务逻辑和用户操作进行灵活的路由控制和跳转。
-
优点:
- 可以根据动态条件触发路由导航,适合处理复杂的路由逻辑。
- 可以在事件处理程序、条件语句等任意位置触发路由操作。
-
示例代码(React Router v6):
import { useNavigate } from 'react-router-dom'; function MyComponent() { const navigate = useNavigate(); function handleClick() { navigate('/about'); } return ( <button onClick={handleClick}>Go to About</button> ); }
-
声明式路由适合静态路由配置,而编程式路由适合根据动态条件进行路由导航和跳转。
在实际应用中,两种方式通常会结合使用,以满足不同的需求。
实例
React-Router V5 实例
React-Router 提供了一种声明式的方式来定义路由表,这使得我们可以清晰地描述页面导航关系。以下是一个使用声明式路由表的示例:
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import Home from './components/Home';
import About from './components/About';
import Contact from './components/Contact';
import NotFound from './components/NotFound';
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About },
{ path: '/contact', component: Contact },
];
function App() {
return (
<Router>
<Switch>
{routes.map(route => (
<Route
key={route.path}
exact
path={route.path}
component={route.component}
/>
))}
<Route component={NotFound} />
</Switch>
</Router>
);
}
export default App;
在上述示例中,我们首先创建了一个名为 routes
的数组,用于存储每个路径对应的组件信息。每个路由对象都包含 path
和 component
两个属性。
接下来,在 App
组件中,我们使用 <Switch>
组件包裹了多个 <Route>
组件。在 <Switch>
内部,我们通过 routes.map()
方法遍历路由数组,并为每个路由对象创建一个 <Route>
组件。
在每个 <Route>
组件中,我们通过 key
属性给每个路由对象提供唯一的标识符,并使用 exact
精确匹配路径。然后,我们分别传入 path
和 component
属性,将路由路径与对应的组件进行关联。
最后,我们使用 <Route component={NotFound} />
定义了一个默认的路由,用于匹配无法找到对应组件的情况。
通过这种声明式的方式,我们可以更加清晰地定义整个应用程序的路由表,使代码更易于维护和理解。记得在使用 React-Router 时导入相关组件,并正确配置路由容器(如 BrowserRouter
)。
React-Router V6 实例
React-Router V6 的基本用法包括以下几个步骤:
- 安装 react-router-dom 库,使用 npm 或 yarn 命令。
- 在 React 项目的根文件(index.js)中导入 BrowserRouter 组件,包裹 App 组件。
- 在 App 组件中导入 Routes, Route, Link 等组件,创建路由和导航。
- 使用嵌套路由、index 路由、通配符等方式来实现复杂的路由功能。
- 使用 useParams, useSearchParams, useNavigate 等 hooks 来获取参数和跳转路由。
下面是一个使用 React-Router V6 的示例代码:
首先,确保你已经安装了相关的依赖包 react-router-dom
和 react
。
创建一个名为 App.js
的组件,并设置路由规则:
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import Home from './Home';
import About from './About';
import NotFound from './NotFound';
function App() {
return (
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="*" element={<NotFound />} />
</Routes>
</Router>
);
}
export default App;
在 Home.js
、About.js
和 NotFound.js
中,你可以编写对应的组件内容,例如:
Home.js:
function Home() {
return <h1>Welcome to the Home page!</h1>;
}
export default Home;
About.js:
function About() {
return <h1>About Us</h1>;
}
export default About;
NotFound.js:
function NotFound() {
return <h1>404 - Page Not Found</h1>;
}
export default NotFound;
在根组件中引入并渲染 App.js
组件:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
以上代码示例中,我们使用 React-Router V6 来设置三个路由:根路径 '/'
、关于页面 '/about'
和通配符路径 '*'
用于处理找不到的页面。你可以根据自己的需求添加更多的路由和组件。
React-Router V6 在使用上与 V5 有所不同,例如使用 <Routes>
组件替代之前的 <Switch>
,<Route>
组件中的 path
属性改为在元素上直接使用 path
属性。请确保查阅 React-Router V6 的文档以获取更多详细信息和示例。
react router v6 路由表实例(参考)
安装路由并配置路由表
首先,我们先安装路由:npm i react-router-dom --save 或者yarn add react-router-dom --save,现在默认安装的式router6.x
//安装路由
npm i react-router-dom --save 或者
yarn add react-router-dom --save
创建好需要的目录
在routers文件夹下的index.js中配置路由
import { Navigate } from 'react-router-dom'
import Message from '../pages/Message/Message'
import News from '../pages/News/News'
import About from '../pages/About/About'
import Home from '../pages/Home/Home'
import Detail from '../pages/Message/Detail/Detail'
export default [
{
path: '/about',
element: <About/>
},
{
path: '/home', //路径,这里式一级路由,所以在路径前要带上'/'
element: <Home/>, //在该路径下需要渲染的元素节点
children: [ //home下的子路由 //该路径下的子路由
{
path: 'message', //子路由的路径,不需要'/',如果加了'/'则会覆盖掉前面的路由路径
element: <Message/>, //子路由下需要渲染的元素节点
children: [ //message下的子路由 //子路由的子路由,下面称为孙子路由
{
//使用params的方式传参时,需要在path上声明接收参数
// path: 'detail/:id/:title/:msg',
path: 'detail', //孙子路由的路径,一样不需要'/',否则将会覆盖前面的路由路径
element: <Detail/> //孙子路由下需要渲染的元素节点
}
]
},
{
path: 'news',
element: <News/>,
children: [
{
path: 'detail',
element: <Detail/>
}
]
},
]
},
{
path: '/', //当路径的端口号后面没有地址时就渲染'<Navigate>'
element: <Navigate to="/about"/> //只要`<Navigate>`组件被渲染,就会修改路径,切换视图
}
]
创建一级路由
创建路由页面,在App.js里面写一级路由,并写一个简单的样式
import { NavLink, useRoutes } from 'react-router-dom'
import routes from './routers' //导入路由表
import('./App.css') //导入APP.js
function App () {
const element = useRoutes(routes)
return (
<div>
<div className="nav">
<NavLink className='navLink' to="/about">About</NavLink>
<NavLink className='navLink' to="/home">Home</NavLink>
</div>
<div className="main">
{element}
</div>
</div>
)
}
export default App
Redux
对 Redux 的理解,主要解决什么问题
React 是视图层框架。Redux 是一个用来管理数据状态和 UI 状态的JavaScript 应用工具。
随着 JavaScript 单页应用(SPA)开发日趋复杂, JavaScript 需要管理比任何时候都要多的 state(状态),Redux 就是降低管理难度的。(Redux 支持React、Angular、jQuery甚至纯JavaScript)
在 React 中,UI 以组件的形式来搭建,组件之间可以嵌套组合。但React 中 组件间通信的数据流是单向的,顶层组件可以通过 props属性向下层组件传递数据,而下层组件不能向上层组件传递数据,兄弟组件之间同样不能。这样简单的单向数据流支撑起了 React 中的数据可控性。
当项目越来越大的时候,管理数据的事件或回调函数将越来越多,也将越来越不好管理。 管理不断变化的 state 非常困难。如果一个model 的变化会引起另一个 model 变化,那么当 view 变化时,就可能引起对应 model 以及另一个 model 的变化,依次地,可能会引起另一个 view 的变化。直至你搞不清楚到底发生了什么。state 在什么时候,由于什么原因,如何变化已然不受控制。 当系统变得错综复杂的时候,想重现问题或者添加新功能就会变得举步维艰。如果这还不够糟糕,考虑一些来自前端开发领域的新需求,如更新调优、服务端渲染、路由跳转前请求数据等。state 的管理在大项目中相当复杂。
Redux 提供了一个叫 store 的统一仓储库 ,组件通过 dispatch 将state 直接传入 store, 不用通过其他的组件。并且组件通过 subscribe 从 store 获取到 state 的改变。使用了 Redux,所有的组件都可以从 store 中获取到所需的 state,他们也能从 store获取到 state 的改变。这比组件之间互相传递数据清晰明朗的多。
主要解决的问题:
单纯的Redux 只是一个状态机,是没有UI 呈现的 ,react- redux 作用是将Redux 的状态机和React 的UI 呈现绑定在一起,当你dispatch action 改变state 的时候,会自动更新页面。
Redux 状态管理器和变量挂载到 window 中有什么区别
两者都是存储数据以供后期使用。
但是 Redux 状态更改可回溯——Time travel,数据多了的时候可以很清晰的知道改动在哪里发生,完整的提供了一套状态管理模式。
随着 JavaScript 单页应用开发日趋复杂,JavaScript 需要管理比任何时候都要多的 state (状态)。 这些 state 可能包括服务器响应、缓存数据、本地生成尚未持久化到服务器的数据,也包括 UI状态,如激活的路由,被选中的标签,是否显示加载动效或者分页器等等。
管理不断变化的 state 非常困难。如果一个 model 的变化会引起另一个 model 变化,那么当 view 变化时,就可能引起对应 model 以及另一个model 的变化,依次地,可能会引起另一个 view 的变化。直至你搞不清楚到底发生了什么。state 在什么时候,由于什么原因,如何变化已然不受控制。 当系统变得错综复杂的时候,想重现问题或者添加新功能就会变得举步维艰。
如果这还不够糟糕,考虑一些来自前端开发领域的新需求,如更新调优、服务端渲染、路由跳转前请求数据等等。前端开发者正在经受前所未有的复杂性,难道就这么放弃了吗?当然不是。
这里的复杂性很大程度上来自于:我们总是将两个难以理清的概念混淆在一起:变化和异步。 可以称它们为曼妥思和可乐。如果把二者分开,能做的很好,但混到一起,就变得一团糟。一些库如 React 视图在视图层禁止异步和直接操作 DOM 来解决这个问题。美中不足的是,React 依旧把处理 state 中数据的问题留给了你。Redux 就是为了帮你解决这个问题。
Redux 和 Vuex 有什么区别,它们的共同思想
(1) Redux 和 Vuex 区别
Vuex 改进了 Redux 中的Action 和Reducer 函数,以 mutations 变化函数取代 Reducer,无需 switch,只需在对应的mutation 函数里改变 state 值即可
Vuex 由于 Vue 自动重新渲染的特性,无需订阅重新渲染函数,只要生成新的State 即可
Vuex 数据流的顺序是∶View 调用 store.commit 提交对应的请求到Store 中对应的mutation 函数->store 改变(vue 检测到数据变化自动渲染)
通俗点理解就是,vuex 弱化 dispatch,通过commit 进行 store 状态的一次更变;取消了 action 概念,不必传入特定的 action 形式进行指定变更;弱化 reducer,基于 commit 参数直接对数据进行转变,使得框架更加简易;
(2) 共同思想
单—的数据源
变化可以预测
本质上∶ redux 与 vuex 都是对 mvvm 思想的服务,将数据从视图中抽离的一种方案。
Redux 中间件是怎么拿到 store 和 action? 然后怎么处理?
redux 中间件本质就是一个函数柯里化。
redux applyMiddleware Api源码中每个middleware 接受 2 个参数, Store 的getState 函数和dispatch 函数,分别获得store 和action,最终返回一个函数。
该函数会被传入 next 的下一个 middleware 的 dispatch 方法,并返回一个接收 action 的新函数, 这个函数可以直接调用 next(action),或者在其他需要的时刻调用,甚至根本不去调用它。
调用链中最后一个 middleware 会接受真实的 store 的 dispatch 方法作为 next 参数,并借此结束调用链。
所以,middleware 的函数签名是({ getState,dispatch })=> next => action。
扩展:函数柯里化
在 JavaScript 中,函数柯里化是 一种将接收多个参数的函数转换为接收单个参数的函数序列的技术。
函数柯里化可以帮助我们创建更加灵活和可组合的函数。以下是几种实现函数柯里化的方法:
- 使用闭包和递归:
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(null, args);
} else {
return function (...args2) {
return curried.apply(null, args.concat(args2));
};
}
};
}
// 示例使用:
function add(x, y, z) {
return x + y + z;
}
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 输出 6
console.log(curriedAdd(1, 2)(3)); // 输出 6
console.log(curriedAdd(1)(2, 3)); // 输出 6
console.log(curriedAdd(1, 2, 3)); // 输出 6
- 使用 bind 方法:
function add(x, y, z) {
return x + y + z;
}
const curriedAdd = add.bind(null, 1).bind(null, 2);
console.log(curriedAdd(3)); // 输出 6
- 使用箭头函数和递归:
const curry = (fn) => {
const curried = (...args) =>
args.length >= fn.length
? fn(...args)
: (...args2) => curried(...args, ...args2);
return curried;
};
// 示例使用同第一个方法
这些方法都可以实现函数柯里化。你可以根据个人偏好和项目需求选择适合的方法。无论哪种方法,函数柯里化都能让函数更加灵活、可复用和可组合,提供更好的函数编程体验。
参考文档
- https://blog.csdn.net/weixin_46360938/article/details/126747219