循序渐进学 react <三>

续 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 来完成过渡动画效果
        • 包裹元素
    • SwitchTransition
      • 两个组件显示和隐藏切换时,使用该组件
    • TransitionGroup
      • 将多个动画组件包裹在其中,一般用于列表中元素的动画

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
  • 自己添加类非常麻烦,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 可以返回
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
  • 双标签嵌套 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;

  • 16
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值