React.js学习(二):案例源码学习“排序动画”

发布于:2022-12-07 ⋅ 阅读:(834) ⋅ 点赞:(0)

又有时间了,继续推进React的第二天。自己学习React往往会忽视许多细节,于是找来别人的项目学习学习。今天学习一个小案例,“排序动画”。

案例的核心是“状态管理”

一、模块划分

阅读App.jsx文件,可以看到这个可视化的排序页面主要分为4个部分

  • NavBar 导航栏:主要功能选择排序算法
  • Controller 控制栏:生成无序数组和控制排序动画的速度、暂停和播放
  • AlgoDisplay 排序算法演示板:根据所选择的算法,演示排序的动画
  • Footer 页脚:一些补充信息,这里是作者Sadanand的署名和链接
export default function App() {
  return (
    <Container>
      <NavBar />
      <Controller />
      <AlgoDisplay />
      <Footer />
    </Container>
  );
}

 二、逻辑分析

分析一下如果自己去实现,需要完成哪些信息处理逻辑。

这里,四个核心模块都是同一级的兄弟元素,因此第一个需要考虑的是:

  • 1. 如何条理清晰的进行模块间信息传递?

关于每一个模块:

  • 2. NavBar 需要做的处理比较简单,只需要把用户点击的算法对应的名称存入状态
  • 3. Controller的生成数组比较容易,控制动画的速度也可以通过调整动画的帧率(每一帧间隔时长)来完成,最后就是动画的暂停和播放,需要状态来切换 
  • 4. Footer 没有太多的责任,当作一个Label来完成即可

上面的4条,其实正在关键的是第一条,因为随着项目规模的增大,麻烦的不是实现一个模块,而是井井有序的控制每个模块之间的关系。

三、 实现细节

1. 配置基本动画变量config.js

import { getScreenWidth } from "./helper";
import { BubbleSort } from "../sortFunctions/BubbleSort";
import { SelectionSort } from "../sortFunctions/SelectionSort";
import { InsertionSort } from "../sortFunctions/InsertionSort";
import { QuickSort } from "../sortFunctions/QuickSort";
import { HeapSort } from "../sortFunctions/HeapSort.js";
import { MergeSort } from "../sortFunctions/MergeSort";

// 演示动画中,数字不同状态时的颜色
export const comparisionColor = "pink";
export const swapColor = "yellow";
export const sortedColor = "springgreen";
export const pivotColor = "sandybrown";

// 演示动画的帧数
export let swapTime = 1000; // 3秒内的帧数
export let compareTime = 500; // 1.5秒内的帧数

// 根据屏幕宽度,设定初始的数组
export let sortingArray = initArrayForScreenSize();

// 所有算法信息
export const sortingAlgorithms = [
  { component: BubbleSort, title: "Bubble", name: "BubbleSort" },
  { component: SelectionSort, title: "Selection", name: "SelectionSort" },
  { component: InsertionSort, title: "Insertion", name: "InsertionSort" },
  { component: HeapSort, title: "Heap", name: "HeapSort" },
  { component: MergeSort, title: "Merge", name: "MergeSort" },
  { component: QuickSort, title: "Quick", name: "QuickSort" },
];

function initArrayForScreenSize() {
  const screenSize = getScreenWidth();
  if (screenSize < 460) return [4, 3, 2, 1];
  else if (screenSize < 720) return [8, 7, 6, 5, 4, 3, 2, 1];
  return [12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1];
}

2. 辅助函数

// 将输入框中的字符串转换为数组字符串,意思就是当往输入框中输入非数字逗号的时候,输入的字符会被清除掉
// 比如 输入 1,2,3,4 正常,当后续输入一个a的时候,输入框不会显示a
export function convertInputToArrayString(string) {
  string = string.replaceAll(/\s/g, "");
  string = string.replaceAll(/\d{4}/g, "");
  string = string.replaceAll(/\s\s/g, " ");
  string = string.replaceAll(/\s,/g, ",");
  string = string.replaceAll(/,,/g, ",");
  string = string.replaceAll(/[^0-9,\s]/g, "");
  return string;
}
// 将数组字符串转变为数组
export function convertArrayStringToArray(string) {
  return string
    .split(",")
    .filter((v) => v !== "")
    .map((v) => +v);
}
// 生成随机数组
export function getRandomArray(length = generateRandomNumberInRange(5, 30)) {
  return Array.from(new Array(length), () => generateRandomNumberInRange());
}
// 获得屏幕(当前浏览器页面)宽度
export function getScreenWidth(){
  return window.innerWidth;
}
// 设定延迟的函数
export function delay(time) {
  return new Promise((resolve) => setTimeout(resolve, time));
}
// 生成指定范围内的数字, 范围是(lowerLimit, lowerLimit+upperLimit)
function generateRandomNumberInRange(lowerLimit = 0, upperLimit = 999) {
  return lowerLimit + Math.floor(Math.random() * upperLimit);
}

3. zustand状态管理

 create方法会创建一个状态管理器,每次使用这个管理器,需要传入一个自定义的方法来间接控制状态,当管理器实现完后,每次传入的方法应该是很简单的方法。一般是调用状态对象的属性,属性包含了状态值或者setState方法。

import create from "zustand";
import { devtools } from "zustand/middleware";
import {
  sortingArray,
  compareTime,
  swapTime,
  sortingAlgorithms,
} from "./config";
// 创建了一个全局状态管理器,其内包含各个状态以及对应的seState方法
export const useControls = create(
  devtools((set) => ({
    progress: "reset", // 动画的重置、播放、暂停状态
    speed: 3, // 动画的速度
    compareTime: compareTime, // 动画数字比较一次所花费的时间
    swapTime: swapTime, // 交换动画一次所花费的时间
    doneCount: 0, // 当前执行完成的动画个数(当需要同时演示所有动画的时候)
    // 开始排序,则设定progress为start,以此类推
    startSorting: () => set({ progress: "start" }),
    pauseSorting: () => set({ progress: "pause" }),
    resetSorting: () => set({ progress: "reset", doneCount: 0 }),
    // 标记动画已经完成:
      // 如果当前选择的是最后一个,即(ALL选项)同时演示所有算法
      // --如果已经完成所有算法, 设置完成算法数量以及progress为done
      // --否则,将doenCount加一,表示当前完成了一个算法
      // 否则直接将doneCount加一,因为非ALL选项,都只有一个算法,完成即表示所有完成
    markSortngDone: () =>
      set((state) => {
        if (useData.getState().algorithm === sortingAlgorithms.length) {
          if (state.doneCount === sortingAlgorithms.length - 1)
            return { doneCount: 0, progress: "done" };
          else return { doneCount: state.doneCount + 1 };
        } else return { progress: "done" };
      }),
    setSpeed: (speed) =>
      set(() => {
        return { swapTime: 3000 / speed, compareTime: 1500 / speed, speed };
      }),
  }))
);

// 设置当前正在执行的算法的状态管理器,包含两个状态:算法的id和算法排序中途的数组
export const useData = create(
  devtools((set) => ({
    // algorithm 表示算法id,如果实现了6种排序算法,
    // 则每一个算法id依次为0,1,2,3,4,5 另外还有一个id=6 表示同时演示所有算法
    algorithm: 0,
    // sortingArray这个就是排序数组,表示当前时刻,此算法对数组排序的结果
    sortingArray: sortingArray,
    // 设置新的数组,可能是经过上一步的排序,得到的新数组
    setSortingArray: (array) => set({ sortingArray: array }),
    // 设置算法的id  
    setAlgorithm: (idx) => set({ algorithm: idx }),
  }))
);

4.总结

 这里状态管理的代码就阅读完了,同时整个项目的实现也大致可以猜到了,就是借助全局的状态管理器,来完成兄弟元素间的信息传递,所有的模块只需要跟useData和useControls来交流即可。