概述
(1)SPA应用
单页面web应用(Single Page Web Application)
是一种网站模型,可以动态重写当前页面,而不需要重新加载整个页面
相对于传统的Web应用它能做到前后端分离,后端只负责处理数据提供接口
页面逻辑和渲染都交给前端
SPA的一个重要实现就是改变路由时页面不刷新
实现这个功能通常有两种方式:windows.history对象或者location.hash
路由分类
(1)后端路由:node服务器端路由,用来处理客户端提交的请求并返回一个响应数据
(2)前端路由:浏览器端路由,当请求的是路由path时,浏览器端前没有发送http请求
但界面会更新显示对应的组件
(2)Hash模式
//原理:利用改变#后面的值不触发网页重载,在客户端实现路由切换
import React from "react";
const Home = () => <div>Home</div>
const About = () => <div>About</div>
const Inbox = () => <div>Inbox</div>
class App extends React.Component {
state = {
path: window.location.hash.substr(1)
}
render() {
let Content
switch (this.state.path) {
case '/home':
Content = Home
break;
case '/about':
Content = About
break;
case '/inbox':
Content = Inbox
break;
default:
break;
}
return (
<div>
<div>Header</div>
<div>
<ul>
<li><a href="#/home">Home</a></li>
<li><a href="#/about">About</a></li>
<li><a href="#/inbox">Inbox</a></li>
</ul>
</div>
<div>
{/*根据变量内容与组件名相匹配来实时刷新*/}
<Content/>
</div>
</div>
)
}
componentDidMount() {
//监听hash值,发生变化则执行回调函数
window.addEventListener('hashchange', () => {
const path = window.location.hash.substr(1)
this.setState({
path
})
})
}
}
export default App;
(3)History模式
调用pushState可以在当前历史记录里添加一条url记录,但页面不会跳转
新增后若页面再发生一次跳转,那么再发生后腿时会跳转到新增url记录上
当前url状态对象可以通过history.state来访问
若要在前进或后退时访问历史记录对象则可以监听window.onpopstate事件
replaceState是替换历史记录中的最新的一条
import React from 'react';
const Home = () => <div>Home Component</div>;
const Page1 = () => <div>Page1 Component</div>;
const Page2 = () => <div>Page2 Component</div>;
const Page3 = () => <div>Page3 Component</div>;
const Page4 = () => <div>Page4 Component</div>;
const route = {
'/': Home,
'/page1': Page1,
'/page2': Page2,
'/page3': Page3,
'/page4': Page4,
}
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
//pathname就是请求地址,例如 /page1
path: window.location.pathname
}
this.initPushstate()
}
//定义方法,在每一次压栈时触发
initPushstate = () => {
//保存原生的pushState
let oldPushState = window.history.pushState;
//重写pushState,添加回调功能
window.history.pushState = function (state, title, pathname) {
// 执行原生pushState
let result = oldPushState.apply(window.history, arguments);
// window添加回调onpushstate
if (typeof window.onpushstate === 'function') {
window.onpushstate({ state, title, pathname, type: 'pushstate' });
}
return result;
}
}
push1 = () => {
window.history.pushState({ page: 'page1' }, { title: 'page1' }, '/page1')
console.log(window.history.length);
}
push2 = () => {
window.history.pushState({ page: 'page2' }, { title: 'page2' }, '/page2')
console.log(window.history.length);
}
push3 = () => {
window.history.pushState({ page: 'page3' }, { title: 'page3' }, '/page3')
console.log(window.history.length);
}
//把最新的历史记录第一条替换,完成重定向,不会触发onpushstate
//例如现在在2,点击3,再点击replace,然后点击回退,这时会跳转到2
//因为把最新的历史记录3替换掉了
replace = () => {
window.history.replaceState({ page: 'page4' }, { title: 'page4' }, '/page4')
console.log(window.history.length);
}
forward = () => {
//加载历史列表中的下一个 URL ,方法的效果等价于点击前进按钮或调用 history.go(1)
window.history.forward()
console.log(window.history.length);
}
back = () => {
window.history.back()
console.log(window.history.length);
}
go = () => {
// window.history.go(1)
window.history.go(-1)
console.log(window.history.length)
}
render() {
let path = this.state.path
let Outlet = route[path]
return (
<div>
<h2>pathname:{this.state.route}</h2>
<button onClick={this.push1}>pushstate1</button>
<button onClick={this.push2}>pushstate2</button>
<button onClick={this.push3}>pushstate3</button>
{/*重定向*/}
<button onClick={this.replace}>replace</button>
<button onClick={this.forward}>forward</button>
<button onClick={this.back}>back</button>
<button onClick={this.go}>go</button>
<Outlet />
</div>
)
}
componentDidMount() {
console.log(window.location);
console.log(window.history);
//在window上加一个onpushstate函数
//模拟onpopstate相同的功能,在每一次压栈操作后触发回调函数
//利用setState把数据同步回去
window.onpushstate = (event) => {
console.log('onpushstate');
this.setState({
//同步路径
path: window.location.pathname
})
}
//前进或后退是触发
window.onpopstate = (e) => {
console.log('onpopstate');
this.setState({
//回退或者前进时能及时切换页面
path: window.location.pathname
})
}
}
}
export default App
(4)react-router
利用react-router实现的案例
先用BrowserRouter把app组件包裹上
link创建链接,route使链接和组件间建立关联,OutLet是路由出口
获取路由详情
在invoices的Link标签中的to方法请求链接,index.js里的Route标签生效,渲染出Invoices组件
渲染位置就在App.js的Outlet中
//index.js配置路由
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { BrowserRouter, Route, Routes } from 'react-router-dom'
import Expenses from './components/expenses'
import Invoices from './components/invoices'
import NoMatch from './components/noMatch'
import Invoice from './components/invoice'
ReactDOM.render(
<BrowserRouter>
<Routes>
{/* 路由嵌套,在APP组件中渲染两个子组件的出口*/}
<Route path='/' element={<App></App>}>
<Route path='expenses' element={<Expenses></Expenses>}></Route>
<Route path='invoices' element={<Invoices></Invoices>}>
{/* 没有ID时展示默认信息 */}
<Route index element={
<main style={{ padding: '5rem' }}>
<p>请选择发票选项</p>
</main>
} />
{/* 根据ID显示单个订单信息 */}
{/*invoiceId是占位符*/}
<Route path=":invoiceId" element={<Invoice />} />
</Route>
{/* 当上面所有的路由不匹配时*会拦截到 */}
<Route path='*' element={<NoMatch></NoMatch>}></Route>
</Route>
</Routes>
</BrowserRouter>
, document.getElementById('root'));
//App.js
import {Link, Outlet} from 'react-router-dom'
function App(){
return(
<div>
<h1>账本</h1>
<nav style={{borderBottom:'solid 1px',paddingBottom:'1rem'}}>
<Link to='/invoices'>发票</Link>
{' || '}
<Link to='/expenses'>花销</Link>
</nav>
{/* 路由出口 */}
<Outlet></Outlet>
</div>
)
}
export default App
//data.js模拟数据
let invoices = [
{
number: 1,
name: 'phone1',
amount: '&80',
date: '06/07/2001'
},
{
number: 2,
name: 'phone2',
amount: '&90',
date: '06/09/2001'
},
{
number: 3,
name: 'iphone3',
amount: '&70',
date: '06/11/2001'
},
{
number: 4,
name: 'iphone4',
amount: '&70',
date: '06/11/2001'
},
{
number: 5,
name: 'apple5',
amount: '&70',
date: '06/11/2001'
},
{
number: 6,
name: 'apple6',
amount: '&70',
date: '06/11/2001'
},
]
export function getInvoices(){
return invoices
}
export function getInvoice(number) {
return invoices.find(
invoice => invoice.number === number
);
}
//expenses.js
export default function Expenses(){
return(
<main style={{padding:'1rem 0'}}>
<h2>花销</h2>
</main>
)
}
//invoice.js
import { useParams } from "react-router-dom";
import { getInvoice } from "../data";
export default function Invoice() {
//params就是一个对象,对象的键名就是index.js中path后的字符串,也就是invoiceId
//键值就是invoices.js中to标签${}里对应的值,点击时能获取到
let params = useParams();
let invoice = getInvoice(parseInt(params.invoiceId, 10));
return (
<main style={{ padding: "1rem" }}>
<h2>总额: {invoice.amount}</h2>
<p>
{invoice.name}: {invoice.number}
</p>
<p>日期: {invoice.date}</p>
</main>
);
}
//invoices.js
import { getInvoices } from "../data"
import { Outlet, useSearchParams } from "react-router-dom"
import { NavLink } from "react-router-dom"
export default function Invoices() {
let invoices = getInvoices()
// useSearchParams返回值是一个数组,从中结构出这两个方法
let [searchParams, setSearchParams] = useSearchParams()
return (
<div style={{ display: 'flex' }}>
<nav
style={{
borderRight: 'solid 1px',
padding: '1rem'
}}
>
{/* 搜索 */}
<input
//searchParams是搜索参数,从地址栏拿到值,设置为输入框默认值
//刷新页面时保证搜索框值还在
value={searchParams.get('filter') || ""}
onChange={
(e) => {
let value = e.target.value
if (value) {
//url设置搜索参数
setSearchParams({ filter: value })
} else {
setSearchParams({})
}
}
}
/>
{/* 发票列表 */}
{
invoices
.filter(invoice => {
//过滤参数
let filter = searchParams.get('filter')
//若没有filter则不过滤了,数据全都返回
if (!filter) return true
//全转成大写
let name = invoice.name.toLocaleLowerCase()
//startsWith 以...开头,返回布尔值
//若为true则留在invoices数组中
return name.startsWith(filter.toLocaleLowerCase())
})
.map(invoice => (
// NavLink是导航链接,能动态改变样式
// 点击NavLink是给style传参数
// style中的函数接收一个对象
// 其中包含isActive属性,是布尔类型,代表是否点击标签
// style返回值是对象
// 动态样式就是利用返回对象实现的
// <NavLink style={() => ({
// })}
// >
// </NavLink>
// 从对象里结构出来isActive
<NavLink style={({ isActive }) => ({
display: 'block',
margin: '1rem 0',
color: isActive ? "red" : ""
})}
//动态地址
to={`/invoices/${invoice.number}`}
key={invoice.number}
>
{invoice.name}
</NavLink>
))
}
</nav>
<Outlet></Outlet>
</div>
)
}
//noMatch.jsx
function NoMatch(){
return(
<main style={{padding:'1rem'}}>
<p>404</p>
</main>
)
}
export default NoMatch
(5)自定义路由组件
改进前查询出新的列表后点击选项又会跳回该页面
这是因为点击选项时路由地址有变回去了
选项的路由地址没有拼接上搜索条件
所以我们给原有的<NavLink/>加强,重新定义一个组件
QueryNavLink.js
import { NavLink, useLocation } from "react-router-dom"
//把to解构出来,其他属性压回到props里
export function QueryLNavLink({to,...props}){
//useLocation返回的是url信息
let location = useLocation()
console.log(location);
//只改变to,其他属性正常传入
return <NavLink to={to+location.search} {...props}></NavLink>
}
再把invoices.js的NavLink标签换成QueryNavLink就行了
最后
以上就是成就海燕为你收集整理的React复习(12)的全部内容,希望文章能够帮你解决React复习(12)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复