续 react <二>~
七、React 过渡动画实现
7.1 react-transition-group
-
可以看成第三方库,由 react 社区提供
-
在开发中,想要给一个组件的显示和消失添加某种过渡动画,可以很好的增加用户体验
-
可以通过原生的 CSS 来实现这些过渡动画,但是 React 社区提供了react-transition-group 用来完成过渡动画
-
React 曾为开发者提供过动画插件 react-addons-css-transition-group,后由社区维护,形成了现在的 react-transition-group
- 这个库可以方便的实现组件的 入场 和 离场 动画,使用时需要进行额外的安装
-
安装:
# npm
npm install react-transition-group
# yarn
yarn add react-transition-group
- react-transition-group 本身非常小,不会为应用程序增加过多的负担
7.2 react-transition-group 主要组件
- react-transition-group主要包含四个组件:
- Transition
- 该组件是一个和平台无关的组件(不一定要结合CSS)
- 在前端开发中,一般是结合CSS来完成样式,所以比较常用的是CSSTransition
- CSSTransition
- 在前端开发中,通常使用 CSSTransition 来完成过渡动画效果
- 包裹元素
- 在前端开发中,通常使用 CSSTransition 来完成过渡动画效果
- SwitchTransition
- 两个组件显示和隐藏切换时,使用该组件
- TransitionGroup
- 将多个动画组件包裹在其中,一般用于列表中元素的动画
- Transition
7.3 CSSTransition
- CSSTransition 是基于 Transition 组件构建的:
- CSSTransition 执行过程中,有三个状态:appear、enter、exit
- 它们有三种状态,需要定义对应的 CSS 样式:
- 第一类,开始状态:对于的类是 -appear、-enter、exit
- 第二类:执行动画:对应的类是-appear-active、-enter-active、-exit-active
- 第三类:执行结束:对应的类是-appear-done、-enter-done、-exit-done
/* 进入动画 */
/* .lili-appear {
transform: translateX(-150px);
}
.lili-appear-active {
transform: translateX(0);
transition: transform 2s ease;
} */
.lili-enter,
.lili-appear {
opacity: 0;
}
.lili-enter-active,
.lili-appear-active {
opacity: 1;
transition: opacity 2s ease;
}
/* 离开动画 */
.lili-exit {
opacity: 1;
}
.lili-exit-active {
opacity: 0;
transition: opacity 2s ease;
}
import React, { PureComponent } from "react";
import { CSSTransition } from "react-transition-group";
import "./style.css";
export class App extends PureComponent {
constructor(props) {
super(props);
this.state = {
isShow: false,
};
}
render() {
const { isShow } = this.state;
return (
<div>
<button onClick={(e) => this.setState({ isShow: !isShow })}>按钮</button>
{/* {isShow && <h2>hahhh</h2>} */}
<CSSTransition in={isShow} classNames='lili' timeout={2000} unmountOnExit={true} appear onEnter={(e) => console.log("开始进入动画")} onEntering={(e) => console.log("执行进入动画")} onEntered={(e) => console.log("执行进入结束")} onExit={(e) => console.log("开始离开动画")} onExiting={(e) => console.log("执行离开动画")} onExited={(e) => console.log("执行离开结束")}>
<h2>哈哈哈哈</h2>
</CSSTransition>
</div>
);
}
}
export default App;
-
CSSTransition 常见对应的属性:
- in:触发进入或者退出状态
- 如果添加了unmountOnExit={true},那么该组件会在执行退出动画结束后被移除掉
- 当 in为 true 时,触发进入状态,会添加 -enter、-enter-acitve的class开始执行动画,当动画执行结束后,会移除两个class,并且添加-enter-done 的 class
- 当 in为 false 时,触发退出状态,会添加 -exit、-exit-active 的 class 开始执行动画,当动画执行结束后,会移除两个 class,并且添加 -enter-done 的 class
- in:触发进入或者退出状态
-
自己添加类非常麻烦,vue 、react 本质也是提前把类 css 属性定义好,实现类的添加和删除
-
属性
- in:接收布尔类型,判断显示或者隐藏
- classNames
7.4 CSSTransition 常见属性
-
classNames:动画 class 的名称
- 决定了在编写css时,对应的class名称:比如card-enter、card-enter-active、card-enter-done
-
**timeout:**过渡动画的时间
-
appear:是否在初次进入添加动画(需要和 in 同时为 true)
-
unmountOnExit:退出后卸载组件
-
**其他属性可以参考官网来学习:**https://reactcommunity.org/react-transition-group/transition
-
CSSTransition对应的钩子函数:主要为了检测动画的执行过程,来完成一些JavaScript 的操作
- onEnter:在进入动画之前被触发
- onEntering:在应用进入动画时被触发
- onEntered:在应用进入动画结束后被触发
-
消除严格模式的警告
- 使用
createRef()
设置 ref - 不通过 api 直接找组件 nodeRef={this.sectionRef}
- 使用
import React, { PureComponent, createRef } from "react";
import { CSSTransition } from "react-transition-group";
import "./style.css";
export class App extends PureComponent {
constructor(props) {
super(props);
this.state = {
isShow: false,
};
this.sectionRef = createRef();
}
render() {
const { isShow } = this.state;
return (
<div>
<button onClick={(e) => this.setState({ isShow: !isShow })}>按钮</button>
{/* {isShow && <h2>hahhh</h2>} */}
<CSSTransition nodeRef={this.sectionRef} in={isShow} classNames='lili' timeout={2000} unmountOnExit={true} appear onEnter={(e) => console.log("开始进入动画")} onEntering={(e) => console.log("执行进入动画")} onEntered={(e) => console.log("执行进入结束")} onExit={(e) => console.log("开始离开动画")} onExiting={(e) => console.log("执行离开动画")} onExited={(e) => console.log("执行离开结束")}>
<div className='section' ref={this.sectionRef}>
<h2>哈哈哈哈</h2>
</div>
</CSSTransition>
</div>
);
}
}
export default App;
7.5 SwitchTransition
.login-enter {
transform: translateX(100px);
opacity: 0;
}
.login-enter-active {
transform: translateX(0);
opacity: 1;
transition: all 1s ease;
}
.login-exit {
transform: translateX(0);
opacity: 1;
}
.login-exit-active {
transform: translateX(-100px);
opacity: 0;
transition: all 1s ease;
}
import React, { PureComponent } from "react";
import { SwitchTransition, CSSTransition } from "react-transition-group";
import './style.css'
export class App extends PureComponent {
constructor() {
super();
this.state = {
isLogin: true,
};
}
render() {
const { isLogin } = this.state;
return (
<div>
<SwitchTransition mode='out-in'>
<CSSTransition key={isLogin ? "exit" : "login"} classNames='login' timeout={1000}>
<button onClick={(e) => this.setState({ isLogin: !isLogin })}>{isLogin ? "退出" : "登录"}</button>
</CSSTransition>
</SwitchTransition>
</div>
);
}
}
export default App;
-
SwitchTransition 可以完成两个组件之间切换的炫酷动画:
- 比如有一个按钮需要在 on 和 off 之间切换,希望看到 on 先从左侧退出,off 再从右侧进入
- 这个动画在 vue 中被称之为 vue transition modes
- react-transition-group中使用 SwitchTransition 来实现该动画
-
SwitchTransition 中主要有一个属性:mode,有两个值
- in-out:表示新组件先进入,旧组件再移除
- out-in:表示新组件先移除,新组件再进入
-
使用 SwitchTransition
- SwitchTransition 组件里面要有 CSSTransition 或者 Transition 组件,不能直接包裹想要切换的组件
- SwitchTransition 里面的 CSSTransition 或 Transition 组件不再像以前那样接受 in 属性来判断元素是何种状态,取而代之的是 key 属性
- 需要使用两个不同的 key 才能够执行动画
7.6 TransitionGroup
- 直接用 TransitionGroup 换掉 ul
- key 必须是唯一值
- 有一组动画时,需要将这些 CSSTransition 放入到一个 TransitionGroup 中来完成动画
import React, { PureComponent } from "react";
import { TransitionGroup, CSSTransition } from "react-transition-group";
import "./style.css";
export class App extends PureComponent {
constructor() {
super();
this.state = {
books: [
{ id:11,name: "你不知道的JS", price: 99 },
{ id:22,name: "JS高级程序设计", price: 99 },
{ id:33,name: "Vuejs高级设计", price: 88 },
],
};
}
addNewBook() {
const books = [...this.state.books];
books.push({ id:new Date().getTime(),name: "lili", price: 99 });
this.setState({ books });
}
removeBook(index) {
const books = [...this.state.books];
books.splice(index,1)
this.setState({ books });
}
render() {
const { books } = this.state;
return (
<div>
<h2>书籍列表:</h2>
<TransitionGroup component='ul'>
{books.map((item, index) => {
return (
<CSSTransition key={item.id} classNames='book' timeout={1000}>
<li>
<span>
{item.name}-{item.price}
</span>
<button onClick={(e) => this.removeBook(index)}>删除</button>
</li>
</CSSTransition>
);
})}
</TransitionGroup>
<button onClick={(e) => this.addNewBook()}>添加新书籍</button>
</div>
);
}
}
export default App;
.book-enter {
transform: translateX(100px);
opacity: 0;
}
.book-enter-active {
transform: translateX(0);
opacity: 1;
transition: all 1s ease;
}
.book-exit {
transform: translateX(0);
opacity: 1;
}
.book-exit-active {
transform: translateX(150px);
opacity: 0;
transition: all 1s ease;
}
八、React中编写 CSS
8.1 组件化天下的 CSS
- 整个前端已经是组件化的天下
- 而 CSS 的设计就不是为组件化而生的,所以在目前组件化的框架中都在需要一种合适的 CSS 解决方案
- 在组件化中选择合适的 CSS 解决方案应该符合以下条件:
- 可以编写局部 css:css 具备自己的具备作用域,不会随意污染其他组件内的元素
- 可以编写动态的 css:可以获取当前组件的一些状态,根据状态的变化生成不同的 css 样式
- 支持所有的 css 特性:伪类、动画、媒体查询等
- 编写起来简洁方便、最好符合一贯的 css 风格特点
8.2 React 中的 CSS
-
事实上,css 一直是 React 的痛点,也是被很多开发者吐槽、诟病的一个点
-
在这一点上,Vue 做的要好于 React :
- Vue 通过在 .vue 文件中编写
8.3 内联样式
-
内联样式是官方推荐的一种 css 样式的写法:
- style 接受一个采用小驼峰命名属性的 JavaScript 对象,而不是 CSS 字符串
- 并且可以引用 state 中的状态来设置相关的样式
-
内联样式的优点:
- 内联样式, 样式之间不会有冲突
- 可以动态获取当前 state 中的状态
-
内联样式的缺点:
- 写法上都需要使用驼峰标识
- 某些样式没有提示
- 大量的样式, 代码混乱
- 某些样式无法编写(比如伪类/伪元素)
-
所以官方依然是希望内联样式和普通的 css 来结合编写
import React, { PureComponent } from "react";
export class App extends PureComponent {
constructor() {
super();
this.state = {
titleSize: 30,
};
}
addTitleSize() {
this.setState({ titleSize: this.state.titleSize + 1 });
}
render() {
const { titleSize } = this.state;
return (
<div>
<button onClick={(e) => this.addTitleSize()}>change fontSize</button>
<h2 style={{ color: "red", fontSize: `${titleSize}px` }}>hahh</h2>
<p style={{ color: "blue" }}>hahh</p>
</div>
);
}
}
export default App;
8.4 普通的css
- 普通的 css 通常会编写到一个单独的文件,之后再进行引入
- 这样的编写方式和普通的网页开发中编写方式是一致的:
- 如果按照普通的网页标准去编写,那么也不会有太大的问题
- 但是组件化开发中总是希望组件是一个独立的模块,即便是样式也只是在自己内部生效,不会相互影响
- 但是普通的 css 都属于全局的 css,样式之间会相互影响
- 这种编写方式最大的问题是样式之间会相互层叠掉
- 需要进行引入
8.5 css modules
- css modules 并不是 React 特有的解决方案,而是所有使用了类似于webpack配置的环境下都可以使用的
- 如果在其他项目中使用它,那么需要自己来进行配置,比如配置 webpack.config.js 中的 modules: true 等
- React 的脚手架已经内置了css modules 的配置
- .css/.less/.scss 等样式文件都需要修改成 .module.css/.module.less/.module.scss 等
- 之后就可以引用并且进行使用了
- css modules 确实解决了局部作用域的问题,也是很多人喜欢在 React 中使用的一种方案
- 但是这种方案也有自己的缺陷
- 引用的类名,不能使用连接符 (.home-title),在 JavaScript 中是不识别的
- 所有的 className 都必须使用 {style.className} 的形式来编写
- 不方便动态来修改某些样式,依然需要使用内联样式的方式
import React, { PureComponent } from 'react'
import appStyle from './App.module.css'
import Home from './Home/Home'
export class App extends PureComponent {
render() {
return (
<div>
<div className={appStyle.title}>title</div>
<div className={appStyle.content}>content</div>
<Home></Home>
</div>
)
}
}
export default App
.title{
font-style: italic;
color: aquamarine;
}
.content{
color: #aaaaff;
}
import React, { PureComponent } from 'react'
import homeStyle from './Home.module.css'
export class Home extends PureComponent {
render() {
return (
<div className={homeStyle.title}>Home</div>
)
}
}
export default Home
.title{
color: rgb(126, 28, 175);
}
8.6 CSS in JS
- css in js
- “CSS-in-JS” 是指一种模式,其中 CSS 由 JavaScript 生成而不是在外部文件中定义
- 注意此功能并不是 React 的一部分,而是由第三方库提供
- React 对样式如何定义并没有明确态度
- 在传统的前端开发中,通常会将结构(HTML)、样式(CSS)、逻辑(JavaScript)进行分离
- React 的思想中认为逻辑本身和 UI 是无法分离的,所以才会有了 JSX 的语法
- **样式呢?**样式也是属于 UI 的一部分
- 事实上 CSS-in-JS 的模式就是一种将样式(CSS)也写入到 JavaScript 中的方式,并且可以方便的使用 JavaScript 的状态
- 所以 React有被人称之为 All in JS
- 当然,这种开发的方式也受到了很多的批评:
- Stop using CSS in JavaScript for web development
- https://hackernoon.com/stop-using-css-in-javascript-for-web-development-fa32fb873dcc
- html in js => jsx
8.7 styled-components
- 批评声音虽然有,但是很多优秀的 CSS-in-JS 的库依然非常强大、方便:
- CSS-in-JS 通过 JavaScript 来为 CSS 赋予一些能力,包括类似于 CSS 预处理器一样的样式嵌套、函数定义、逻辑复用、动态修改状态等等
- 虽然 CSS 预处理器也具备某些能力,但是获取动态状态依然是一个不好处理的点
- 所以,目前可以说 CSS-in-JS 是 React 编写 CSS 最为受欢迎的一种解决方案
- 目前比较流行的 CSS-in-JS 的库
- styled-components
- emotion
- glamorous
- 目前可以说 styled-components 依然是社区最流行的 CSS-in-JS 库
- 安装 styled-components:
npm i styled-components
8.8 ES6 标签模板字符串
- 模板字符串还有另外一种用法:标签模板字符串(Tagged Template Literals)
- 一个普通的 JavaScript 的函数:
- 正常情况下,都是通过 函数名() 方式来进行调用的,其实函数还有另外一种调用方式
- 如果在调用的时候插入其他的变量:
- 模板字符串被拆分了
- 第一个元素是数组,是被模块字符串拆分的字符串组合
- 后面的元素是一个个模块字符串传入的内容
- 标签模板字符串使用
// ES6 标签模板字符串
const name = "lili";
const age = 18;
// 1. 模板字符串的基本使用
const str = `my name is ${name},age is ${age}`;
console.log(str);
// 2. 标签模板字符串的使用
function foo(...args) {
console.log(args);
}
foo("lili", 18);
foo`my name is ${name},age is ${age}`;
- 在 styled component 中,就是通过这种方式来解析模块字符串,最终生成想要的样式的
- 安装插件:
vscode-styled-components
8.9 styled-components 使用方法
- 单独创建文件:
style.js
- 本质上使用标签模板字符串
import styled from "styled-components";
// 1. 基本使用
export const AppWrapper = styled.div`
.footer {
border: 1px solid orange;
}
`
// 2. 子元素单独抽取到一个样式组件
export const SectionWrapper = styled.div`
.section {
border: 1px solid red;
.title {
font-size: 30px;
&:hover {
background-color: purple;
}
}
}
`;
App.jsx
import React, { PureComponent } from "react";
import { AppWrapper, SectionWrapper } from "./style";
export class App extends PureComponent {
render() {
return (
<AppWrapper>
<SectionWrapper>
<h2 className='title'>我是标题</h2>
<p className='content'>我是内容哈哈哈</p>
</SectionWrapper>
<div className='footer'>
<p>免责声明</p>
<p>版权声明</p>
</div>
</AppWrapper>
);
}
}
export default App;
8.10 styled 的基本使用
-
styled-components 的本质是通过函数的调用,最终创建出一个组件:
- 这个组件会被自动添加上一个不重复的 class
- styled-components 会给该 class 添加相关的样式
-
另外,它支持类似于 CSS 预处理器一样的样式嵌套:
- 支持直接子代选择器或后代选择器,并且直接编写样式
- 可以通过&符号获取当前元素
- 直接伪类选择器、伪元素等
8.11 props、attrs 属性
-
props 可以传递
-
props 可以被传递给 styled 组件
- 获取 props 需要通过${}传入一个插值函数,props 会作为该函数的参数
- 这种方式可以有效的解决动态样式的问题
-
添加 attrs 属性
- 可以设置默认值
export const primaryColor = "#ff8800";
export const secondColor = "#ff7788";
export const smallSize = "12px";
export const middleSize = "14px";
export const largeSize = "18px";
import React, { PureComponent } from "react";
import { AppWrapper, SectionWrapper } from "./style";
import Home from "./home";
export class App extends PureComponent {
constructor() {
super();
this.state = {
size: 30,
color: "red",
};
}
render() {
const { size, color } = this.state;
return (
<AppWrapper>
<SectionWrapper size={size} color={color}>
<h2 className='title'>我是标题</h2>
<p className='content'>我是内容哈哈哈</p>
<button onClick={(e) => this.setState({ color: "skyblue" })}>修改颜色</button>
</SectionWrapper>
<Home></Home>
<div className='footer'>
<p>免责声明</p>
<p>版权声明</p>
</div>
</AppWrapper>
);
}
}
export default App;
import styled from "styled-components";
import { primaryColor, secondColor, largeSize } from "./style/variables";
// 1. 基本使用
export const AppWrapper = styled.div`
.footer {
border: 1px solid orange;
}
`;
// 2. 子元素单独抽取到一个样式组件
// 3. 可以接收外部传入的props
// 4. 可以通过 attrs 给标签模板字符串中提供属性(默认属性)
// 5. 从一个单独的文件中引入变量
export const SectionWrapper = styled.div.attrs((props) => {
return {
tColor: props.color || "yellow",
};
})`
.title {
font-size: ${(props) => props.size}px;
color: ${(props) => props.tColor};
&:hover {
background-color: purple;
}
}
.content {
font-size: ${largeSize};
border: 1px solid red;
}
`;
8.12 styled 高级特性
- 支持样式的继承
const LiliButton = styled.button`
border: 1px solid red;
border-radius: 6px;
`;
export const LiliButtonWrapper = styled(LiliButton)`
background-color: red;
`;
- styled 设置主题
import React from "react";
import ReactDOM from "react-dom/client";
import { ThemeProvider } from "styled-components";
// import App from './01_内联样式的CSS/App';
// import App from './02_普通的CSS写法/App';
// import App from './02_普通的CSS写法 copy/App';
import App from "./05_CSS_in_js写法/App";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<ThemeProvider theme={{ color: "green", size: "88px" }}>
<App />
</ThemeProvider>
</React.StrictMode>
);
8.13 vue 中添加 class
-
传入一个对象:
v-bind:class ="{active:isActive,'text-danger':hsError}"
-
可以传入一个数组:
v-bind:class ="[activeClass,errorClass]"
-
对象和数组混合使用:
v-bind:class ="[{active:isActive},errorClass]
8.14 React 中添加 class
- 三元运算符
- 设置一个数组
export class App extends PureComponent {
constructor() {
super();
this.state = {
isaaa: true,
isbbb: false,
};
}
render() {
const { isaaa, isbbb } = this.state;
const classList = ["aaaa"];
if (isbbb) classList.push("bbb");
if (isaaa) classList.push("aaa");
const classname = classList.join(" ");
return (
<div>
<h1 className={`bbb ${isaaa ? "aaa" : ""}`}>啊啊aa</h1>
<h2 className={classname}>hhahh</h2>
</div>
);
}
}
- 借助于一个用于动态添加 classnames 的第三方的库:classnames
- 安装:
npm i classnames
- 可以自己命名:cx
- 对象写法:
<h3 className={classNames("aaa", { bbb: isbbb, ccc: isccc })}>hhhhhhhhhh</h3>
- 数组写法:
<h3 className={classNames(["aaa", { bbb: isbbb, ccc: isccc }])}>zzzzzzzzzzzzzzz</h3>
- 安装:
//普通值写法
className = {cx(styles.container,style?.[`container_${curLevel}`],style.mask)}
//对象写法
<div className={cx(styles.mask, {[styles.loading]: isLoading})}>{isLoading ? '加载中...' : ''}</div>
九、**React-Router 路由详解 **
9.1 URL 的 hash
- 前端路由是如何做到URL和内容进行映射呢?监听URL的改变
- URL 的 hash
- URL的 hash也就是锚点(#), 本质上是改变 window.location 的 href 属性
- 可以通过直接赋值 location.hash 来改变 href , 但是页面不发生刷新
- hash 的优势就是兼容性更好,在老版 IE 中都可以运行,但是缺陷是有一个 #,显得不像一个真实的路径
9.2 HTML5 的 History
- history 接口是 HTML5 新增的, 它有六种模式改变 URL 而不刷新页面:
- replaceState:替换原来的路径
- pushState:使用新的路径
- popState:路径的回退
- go:向前或向后改变路径
- forward:向前改变路径
- back:向后改变路径
9.3 react-router
-
目前前端流行的三大框架, 都有自己的路由实现:
- Angular 的 ngRouter
- React 的 ReactRouter
- Vue 的 vue-router
-
React Router在最近两年版本更新的较快,并且在最新的React Router6.x版本中发生了较大的变化
- 目前 React Router6.x 已经非常稳定,可以放心的使用
-
安装 React Router:
- 安装时,选择 react-router-dom
- react-router 会包含一些 react-native 的内容,web开发并不需要
-
npm install react-router-dom
9.4 Router 的基本使用
- react-router 最主要的 API 是提供的一些组件:
- BrowserRouter 或 HashRouter
- Router 中包含了对路径改变的监听,并且会将相应的路径传递给子组件
- BrowserRouter 使用 history 模式
- HashRouter 使用 hash 模式
- 路由最核心的是映射关系
- path => Component
9.5 路由映射配置
- Routes:包裹所有的 Route,在其中匹配一个路由
- Router5.x 使用的是 Switch 组件
- 一般路由配置在 App.jsx 里面
- Route:Route 用于路径的匹配
- path 属性:用于设置匹配到的路径
- element 属性:设置匹配到路径后,渲染的组件
- Router5.x 使用的是 **component **属性
- exact:精准匹配,只有精准匹配到完全一致的路径,才会渲染对应的组件
- Router6.x 不再支持该属性
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { HashRouter } from "react-router-dom";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<HashRouter>
<App />
</HashRouter>
</React.StrictMode>
);
9.6 路由配置和跳转
- Link 和 NavLink
- 通常路径的跳转是使用 Link 组件,最终会被渲染成 a 元素
- NavLink 是在 Link 基础之上增加了一些样式属性
- to 属性:Link 中最重要的属性,用于设置跳转到的路径
- repalce 直接路径替换
- push 可以返回
- repalce 直接路径替换
import React, { PureComponent } from "react";
import { Route, Routes } from "react-router-dom";
import Home from "./pages/Home";
import About from "./pages/About";
import { Link } from "react-router-dom";
export class App extends PureComponent {
render() {
return (
<div className='app'>
<div className='header'>
<span> Header</span>
<div className='nav'>
<Link to='/home'>首页</Link>
<Link to='/about'>关于</Link>
</div>
<hr />
</div>
<div className='content'>
{/* 映射关系:path=>Component */}
<Routes>
<Route path='/home' element={<Home />} />
<Route path='/about' element={<About />} />
</Routes>
</div>
<div className='footer'>
<hr />
Footer
</div>
</div>
);
}
}
export default App;
9.7 NavLink 的使用
-
需求:路径选中时,对应的 a 元素变为红色
-
使用 NavLink 组件来替代 Link 组件
- style:传入函数,函数接受一个对象,包含 isActive 属性
- className:传入函数,函数接受一个对象,包含 isActive 属性
-
默认的 activeClassName:
-
事实上在默认匹配成功时,NavLink就会添加上一个动态的 active class
-
所以也可以直接编写样式
-
-
如果担心这个class在其他地方被使用了,出现样式的层叠,也可以自定义 class
-
方式一
- style 传入一个函数
- 参数解构:isActive
-
方式二
- className
-
<NavLink to='/home' style={(isActive) => ({ color: isActive ? "blue" : "" })}>
首页
</NavLink>
<NavLink to='/about' style={() => ({ color: "red" })}>
关于
</NavLink>
<NavLink to='/home' className={({isActive})=>isActive?"link-active":''}>
首页
</NavLink>
<NavLink to='/about' style={() => ({ color: "red" })}>
关于
</NavLink>
9.8 Navigate 导航
- Navigate 用于路由的重定向,当这个组件出现时,就会执行跳转到对应的 to 路径中
import React, { PureComponent } from 'react'
import { Navigate } from 'react-router-dom'
export class Login extends PureComponent {
constructor(props) {
super(props)
this.state = {
isLogin:false
}
}
login() {
this.setState({isLogin:true})
}
render() {
const {isLogin} = this.state
return (
<div>
<h1>Login Page</h1>
{!isLogin? <button onClick={e=>this.login()}>登录</button>:<Navigate to='/home'/>}
</div>
)
}
}
export default Login
- 在匹配到/的时候,直接跳转到 /home 页面:
<Route path="/" element={ <Navigate to='/home'/>} />
9.9 Not Found 页面配置
<Route path="*" element={ < NotFound/>} />
import React, { PureComponent } from 'react'
export class NotFound extends PureComponent {
render() {
return (
<div>
<h1>NotFound Page</h1>
<p>该页面不存在,请联系开发人员</p>
</div>
)
}
}
export default NotFound
9.10 路由的嵌套
- 外链接直接使用 a 标签
- 组件用于在父路由元素中作为子路由的占位元素
- App.jsx
- Home.jsx
- HomeRecommend.jsx
- HomeRanking.jsx
- Home.jsx
- 双标签嵌套 route
<div className='content'>
{/* 映射关系:path=>Component */}
<Routes>
<Route path="/" element={ <Navigate to='/home'/>} />
<Route path='/home' element={<Home />} >
<Route path="/home" element={ <Navigate to='/home/recommend'/>} />
<Route path="/home/recommend" element={ <HomeRecommend/>} />
<Route path="/home/ranking" element={ <HomeRanking/>} />
</Route>
<Route path='/about' element={<About />} />
<Route path='/login' element={<Login />} />
<Route path="*" element={ < NotFound/>} />
</Routes>
</div>
<div>
<h1>Home Page</h1>
<div className="home-nav">
<Link to='/home/recommend'>推荐</Link>
<Link to='/home/ranking'>排行榜</Link>
</div>
{/* 占位的组件 */}
<Outlet />
</div>
9.11 手动路由的跳转
-
**目前实现的跳转主要是通过 Link 或者 NavLink 进行跳转的,实际上也可以通过 **JavaScript代码进行跳转
-
Navigate 组件是可以进行路由的跳转的,但是依然是组件的方式
-
希望通过JavaScript 代码逻辑进行跳转(比如点击了一个button),那么就需要获取到 navigate 对象
-
useNavigate => hook =>只能在函数式组件中调用
- 改成函数组件
import React from "react"; import { Navigate, Route, Routes,useNavigate} from "react-router-dom"; import Home from "./pages/Home"; import About from "./pages/About"; import { Link} from "react-router-dom"; import Login from "./pages/Login"; import NotFound from "./pages/NotFound"; import HomeRecommend from "./pages/HomeRecommend"; import HomeRanking from "./pages/HomeRanking"; import Category from "./pages/Category"; import Order from "./pages/Order"; export function App(props) { // 必须放在顶层 const navigate = useNavigate() function navigateTo(path) { navigate(path) } return ( <div className='app'> <div className='header'> <span> Header</span> <div className='nav'> <Link to='./home'>首页</Link> <Link to='./about'>关于</Link> <Link to='./login'>登录</Link> <button onClick={e=>navigateTo('/category')}>分类</button> <span onClick={e=>navigateTo('/order')}>订单</span> </div> <hr /> </div> <div className='content'> <Routes> <Route path='/' element={<Navigate to='/home' />} /> <Route path='/home' element={<Home />}> <Route path='/home' element={<Navigate to='/home/recommend' />} /> <Route path='/home/recommend' element={<HomeRecommend />} /> <Route path='/home/ranking' element={<HomeRanking />} /> </Route> <Route path='/about' element={<About />} /> <Route path='/login' element={<Login />} /> <Route path='/category' element={<Category />} /> <Route path='/order' element={<Order />} /> <Route path='*' element={<NotFound />} /> </Routes> </div> <div className='footer'> <hr /> Footer </div> </div> ); } export default App;
-
封装高阶组件
- 对某一个组件的渲染进行增强
import React, { PureComponent } from 'react' import { Link, Outlet, useNavigate } from 'react-router-dom' export class Home extends PureComponent { navigateTo(path) { console.log(this.props.router); const { navigate } = this.props.router navigate(path) } render() { return ( <div> <h1>Home Page</h1> <div className="home-nav"> <Link to='/home/recommend'>推荐</Link> <Link to='/home/ranking'>排行榜</Link> <button onClick={e=>this.navigateTo('/home/songmenu')}>歌单</button> </div> {/* 占位的组件 */} <Outlet /> </div> ) } } // 高阶组件:函数 function withRouter(WrapperComponent) { // 类组件拿不到 navigate,需要使用函数组件 // class NewComponent extends PureComponent{ // render() { // return <WrapperComponent{...this.props} router={{navigate}} /> // } // } return function (props) { const navigate = useNavigate() const router = {navigate} return <WrapperComponent {...props} router={router} /> } } export default withRouter(Home)
-
hoc
- index.js
import withRouter from "./with_router"; export { withRouter }
- with_router
import { useNavigate } from "react-router-dom" function withRouter(WrapperComponent) { return function (props) { const navigate = useNavigate() const router = {navigate} return <WrapperComponent {...props} router={router} /> } } export default withRouter
-
-
在 Router6.x 版本之后,代码类的 API 都迁移到了 hooks 的写法:
- 如果希望进行代码跳转,需要通过 useNavigate 的 Hook获取到 navigate对象进行操作
- 那么如果是一个函数式组件,可以直接调用,但是如果是一个类组件就需要封装高阶组件
9.12 路由参数传递
-
传递参数有二种方式
- 动态路由的方式
- search 传递参数
-
动态路由的概念指的是路由中的路径并不会固定:
-
比如 /detail 的 path 对应一个组件 Detail
-
如果将path在Route匹配时写成/detail/:id,那么 /detail/abc、/detail/123都可以匹配到该 Route,并且进行显示
-
这个匹配规则,就称之为动态路由
-
通常情况下,使用动态路由可以为路由传递参数
import { useNavigate, useParams } from "react-router-dom"
function withRouter(WrapperComponent) {
return function (props) {
const navigate = useNavigate()
const params = useParams()
const router = {navigate,params}
return <WrapperComponent {...props} router={router} />
}
}
export default withRouter
import React, { PureComponent } from 'react'
import { withRouter } from '../hoc'
export class Detail extends PureComponent {
render() {
const { router } = this.props
const {params} = router
return (
<div>
<h1>Detail Page</h1>
<h2>id:{ params.id}</h2>
</div>
)
}
}
export default withRouter(Detail)
import React, { PureComponent } from "react";
import { withRouter } from "../hoc";
export class HomeSongMenu extends PureComponent {
constructor(props) {
super(props);
this.state = {
songMenu: [
{ id: 111, name: "话语留下" },
{ id: 112, name: "你是岁末" },
{ id: 113, name: "古典音乐" },
{ id: 114, name: "话语哈哈" },
],
};
}
navigateToDetail(id) {
console.log(id);
const { navigate } = this.props.router;
navigate("/detail/" + id);
}
render() {
const { songMenu } = this.state;
return (
<div>
<h2>HomeSongMenu</h2>
<ul>
{songMenu.map((item) => (
<li key={item.id} onClick={(e) => this.navigateToDetail(item.id)}>
{item.name}
</li>
))}
</ul>
</div>
);
}
}
export default withRouter(HomeSongMenu);
- search传递参数
import React, { PureComponent } from "react";
import { withRouter } from "../hoc";
export class User extends PureComponent {
render() {
const { router } = this.props;
const { query } = router;
return (
<div>
<h1>User</h1>
<h2>
name:{query.name}-{query.age}
</h2>
</div>
);
}
}
export default withRouter(User);
import { useState } from "react"
import { useLocation, useNavigate, useParams,useSearchParams } from "react-router-dom"
function withRouter(WrapperComponent) {
return function (props) {
const arr = useState(100)
console.log(arr[0]);
console.log(arr[1]);//setCount
// 1. 导航
const navigate = useNavigate()
// 2. 动态路由的参数:/detail/id
const params = useParams()
// 3. 查询字符串的参数:/user?name=lili&age=12
const location = useLocation()
// 返回值是一个数组:直接的值,设置的函数
const [searchParams] = useSearchParams()
// console.log(searchParams.get('name'));
// console.log(searchParams.get('age'));
// 将其转化成普通的对象
// 遍历对象的时候,本质上是遍历 searchParams.entries()
// for(const item of searchParams)
// 转成普通的对象
const query = Object.fromEntries(searchParams)
const router = {navigate,params,location,query}
return <WrapperComponent {...props} router={router} />
}
}
export default withRouter
9.12 路由的配置文件
- react-router 5 是由 react-router-config 这个库配置的
- router 6 不需要库
- 对某些组件进行了异步加载(懒加载),那么需要使用 Suspense 进行包裹
- 包比较大,想要单独打包
- react 本身提供的技术
import Home from "../pages/Home";
import About from "../pages/About";
import Login from "../pages/Login";
import NotFound from "../pages/NotFound";
import HomeRecommend from "../pages/HomeRecommend";
import HomeRanking from "../pages/HomeRanking";
import Category from "../pages/Category";
import Order from "../pages/Order";
import HomeSongMenu from "../pages/HomeSongMenu";
import Detail from "../pages/Detail";
import User from "../pages/User";
import { Navigate } from "react-router-dom";
const routes = [
{
path: "/",
element: <Navigate to='/home' />,
},
{
path: "/home",
element: <Home />,
children: [
{
path: "/home",
element: <Navigate to='/home/recommend' />,
},
{
path: "/home/recommend",
element: <HomeRecommend/>,
},
{
path: "/home/ranking",
element: <HomeRanking/>,
},
{
path: "/home/songmenu",
element: <HomeSongMenu/>,
},
],
},
{
path: "/login",
element: <Login />,
},
{
path: "/about",
element: <About />,
},
{
path: "/category",
element: <Category />,
},
{
path: "/order",
element: <Order />,
},
{
path: "/detail/:id",
element: <Detail />,
},
{
path: "/user",
element: <User />,
},
{
path: "*",
element: <NotFound />,
},
];
export default routes;
- 懒加载
import React, { Suspense } from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { HashRouter } from "react-router-dom";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<HashRouter>
<Suspense fallback={<h3>loading...</h3>}>
<App />
</Suspense>
</HashRouter>
</React.StrictMode>
);
import Home from "../pages/Home";
// import About from "../pages/About";
// import Login from "../pages/Login";
import NotFound from "../pages/NotFound";
import HomeRecommend from "../pages/HomeRecommend";
import HomeRanking from "../pages/HomeRanking";
import Category from "../pages/Category";
import Order from "../pages/Order";
import HomeSongMenu from "../pages/HomeSongMenu";
import Detail from "../pages/Detail";
import User from "../pages/User";
import { Navigate } from "react-router-dom";
import React from "react";
// 进行懒加载
const About = React.lazy(()=>import('../pages/About'))
const Login = React.lazy(()=>import('../pages/Login'))
const routes = [
{
path: "/",
element: <Navigate to='/home' />,
},
{
path: "/home",
element: <Home />,
children: [
{
path: "/home",
element: <Navigate to='/home/recommend' />,
},
{
path: "/home/recommend",
element: <HomeRecommend/>,
},
{
path: "/home/ranking",
element: <HomeRanking/>,
},
{
path: "/home/songmenu",
element: <HomeSongMenu/>,
},
],
},
{
path: "/login",
element: <Login />,
},
{
path: "/about",
element: <About />,
},
{
path: "/category",
element: <Category />,
},
{
path: "/order",
element: <Order />,
},
{
path: "/detail/:id",
element: <Detail />,
},
{
path: "/user",
element: <User />,
},
{
path: "*",
element: <NotFound />,
},
];
export default routes;