[项目实战,源码完整]手把手教你怎么封装功能,React 重写学成在线 IV

Posted GoldenaArcher

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[项目实战,源码完整]手把手教你怎么封装功能,React 重写学成在线 IV相关的知识,希望对你有一定的参考价值。

[项目实战,源码完整]手把手教你怎么封装功能,React 重写学成在线 IV

学完这篇教程,你应该能够掌握以下知识点:

  • 二维数组的使用
  • 动态修改 CSS(行内 CSS)
  • 了解到封装的优势

前情回顾

依旧,目前的项目进度可以在: https://goldenaarcher.com/xczx-react/#/ 上看到。

在上一篇 [项目实战,源码完整]手把手教你怎么封装组件,React 重写学成在线 III 中,首页的内容可以说是完成一半了,此时的效果如下:

这里的实现方法是通过调用两个循环完成的,即首先建立一个伪数据数组:

export const courseSuggestion1 = () => {
  const list = [];
  for (let i = 0; i < 5; i++) {
    const modVal = i % 5;
    let course = null;
    switch (modVal) {
      case 0:
        course = newphpCourse(i);
        break;
      case 1:
        course = newandroidCourse(i);
        break;
      case 2:
        course = newAngularCourse(i);
        break;
      case 3:
      case 4:
        course = newAndroidHybridCourse(i);
        break;
      default:
        break;
    }
    if (i === 0) {
      course.isHot = true;
    } else if (i === 1) {
      course.isNew = true;
    }
    list.push(course);
  }
  return list;
};

然后再通过两次循环调用去实现:

const CourseSuggestion = (props) => {
  // 省略其他内容
  return (
    <div className="flex">
      {courseSuggestion1().map((course) => (
        <CourseItem {...course} key={course.id} />
      ))}
      {courseSuggestion1().map((course) => (
        <CourseItem {...course} key={course.id} />
      ))}
    </div>
  );
};

export default CourseSuggestion;

这其实出现了 2 个问题:

  1. 代码的重复性

    同样的代码出现了两次,并且在复刻以下组件的时候还会出现多复制黏贴:

    还有课程页面:

  2. 细节上的问题——course 的右边距没有处理:

    最右边能看到多出了一部分空间:

所以,本节教程的目的就是以下两个:

  • 实现课程列表的二次封装,一劳永逸的解决这个问题

  • 完成课程以及领域推荐:

    回顾一下,领域推荐的是这样的:

    课程推荐长的是这样的:

对课程列表的二次封装

进行封装后,对课程列表的渲染应该可以满足本页面内所有需求。

修改数组长度

首先,需要将 courseSuggestion1 的长度由 5 改为 10,去渲染一个完整的课程列表:

export const courseSuggestion1 = () => {
  const list = [];
  // 其余部分没变,只改了循环体的长度 ↓
  for (let i = 0; i < 10; i++) {
    // 其余部分没变,只改了循环体的长度 ↑
    // 省略
  }
  return list;
};

二次封装 map 函数

其次,将 array.map() 抽离出来,再进行二次封装。

对于这一步的设计,需要考虑以下 3 点需求:

  1. 怎么样进行合适的断行?

    不断行,所有的课程列表就会挤在同一行:

    显然这不是想要看到的结果

  2. 断行后,怎么解决多余的边距问题?

  3. 怎么样能够应对其他的需求?

    例如说领域榜需要 4 个课程一行,而课程榜需要 5 个课程一行:

    领域课程

请先自己想想看能不能拿出解决方案,开篇就给了提示:

二维数组的使用


解决方案就是通过 二维数组 来解决这个难题:

已知数组的长度,只需要一个参数决定一行渲染多少课程,就可以动态生成一个二维数组,就可以解决 1 和 3 这两个需求。

对于第 2 点,可以通过动态添加行内样式去解决——将一行最后一个课程列表的 margin-right 设置为 0 即可。

具体的封装函数如下:

export const renderCourseInRow = (courseList, numPerRow) => {
  const style = {
    marginRight: 0,
  };
  // 二维数组
  const tableOfCourse = [];
  // 这个变量不是必须的,可以通过 行*列+现在所处的列 得出
  // 不过我个人喜欢加一个参数,这样看起来直观一些
  let currCourseIndex = 0;

  // 第一个循环体,决定多少行数据
  // 使用 Math.ceil() 还是 floor() 就根据业务需求决定了
  for (let row = 0; row < Math.ceil(courseList.length / numPerRow); row++) {
    // 这是每一行的课程
    const rowOfCourse = [];
    // 第二个循环体,将 对应列 的数据放入 rowOfCourse 中
    for (let col = 0; col < numPerRow; col++) {
      const currentCourse = courseList[currCourseIndex++];
      // 这里修改了一下 CourseItem,让它额外接受一个 style 的参数
      rowOfCourse.push(
        <CourseItem
          {...currentCourse}
          style={col === numPerRow - 1 ? style : null}
          key={currentCourse.id}
        />
      );
    }
    tableOfCourse.push(
      <div className="flex" key={`row${row}`}>
        {rowOfCourse}
      </div>
    );
  }
  return tableOfCourse;
};
// 新增引用
import { renderCourseInRow } from '../../../common/courseItem/courseItemUtil';

// 主体内容只需要调用 renderCourseInRow,并且传入合适的参数即可
const CourseSuggestion = (props) => {
  return (
    <div>
      <SubHeader
        subHeaderName="编程入门"
        midConent={subHeaderOl}
        checkMore={checkMore}
      />
      {renderCourseInRow(courseSuggestion1(), 5)}
    </div>
  );
};

export default CourseSuggestion;

实现的效果也还不错,每行最后一个的 margin-right 也被清除了:

完成三个课程列表推荐

这部分其实就没什么难度了,只需要创建合适的伪数据,然后调用 3 遍 CourseSuggestion 这个组件即可。

如之前所分析的,除了传入课程的多寡,以及 sub-header 中参数的不同之外,这两个组件从结构上而言是完全一致的。

主页内容如下:

// 省略其他的引用内容
const Home = () => {
  return (
    <div className="homepage relative">
      <HomeBanner />
      <div className="container">
        <FieldSuggestion />
        <div className="homepage-main">
          <CourseSuggestion
            subHeaderName="精品推荐"
            main={courseSuggestion1()}
          />
          <CourseSuggestion
            subHeaderName="机器学习工程师"
            midConent={subHeaderOl}
            checkMore={checkMore}
            main={mearchineLearning()}
          />
          <CourseSuggestion
            subHeaderName="前端开发工程师"
            midConent={subHeaderOl}
            checkMore={checkMore}
            main={frontend()}
          />
        </div>
      </div>
    </div>
  );
};

export default Home;

效果如下:

封装并完成领域列表

领域列表的结构和课程列表还不太一样,它额外多了两张图片,因此也可以将它单独拆分成一个组件。

对这个组件来说,除了 sub-header 所需要的的参数之外,它自身也许要 3 个参数:

  1. 左侧的图片
  2. 右上的图片
  3. 渲染的 4 个课程

页面的布局依旧可以使用 flex 来实现,而课程的渲染,上面已经封装好了函数,直接调用即可。

这部分的内容实现如下:

import React from 'react';
import SubHeader from '../subHeader';
import { renderCourseInRow } from '../../../common/courseItem/courseItemUtil';

const CourseSuggestionWithBanner = (props) => {
  const { subHeaderName, midConent, checkMore, main, topBanner, sideBanner } =
    props;

  return (
    <div className="homepage-suggestion-list">
      <SubHeader
        subHeaderName={subHeaderName}
        midConent={midConent}
        checkMore={checkMore}
      />
      <div className="flex">
        <div className="course-suggestion-side-banner">
          <img src={sideBanner} alt="homepage-side-banner" />
        </div>
        <div>
          <div className="course-suggestion-top-banner">
            <img src={topBanner} alt="homepage-side-banner" />
          </div>
          {renderCourseInRow(main, 4)}
        </div>
      </div>
    </div>
  );
};

export default CourseSuggestionWithBanner;

封装后的效果:

只需要再微调一下 CSS,这部分的封装就完成了。

最终,这部分的完整展示内容如下:

总结

源码部分在这里:学成在线-react-part3

这一期主要的内容还是在组件的封装上。

二维数组的设计也好,使用行内样式的也用也罢,都是为了能够更进一步的对组件进行更加通用的封装。

封装的过程,也被称之为组件化,其优势除了可以减少代码量之外,也可以让结构变得更加清晰。以完整的 Home 组件为例:

import React from 'react';
import HomeBanner from './homeCarousel';
import './Home.css';
import FieldSuggestion from './fieldSuggestion/fieldSuggestion';
import CourseSuggestion from './courseSuggestion';
import CourseSuggestionWithBanner from './courseSuggestionWithBanner';

import { subHeaderOl, checkMore } from '../../constants/home';
import {
  courseSuggestion1,
  dataAnalyst,
  frontend,
  happyPython,
  mearchineLearning,
} from '../../constants/courseList';

import topBanner from '../../asset/img/home/happy-python.jpg';
import topBanner2 from '../../asset/img/home/python-ai.png';
import sideBanner from '../../asset/img/home/side-banner.png';
import sideBanner2 from '../../asset/img/home/side-banner2.png';

const Home = () => {
  return (
    <div className="homepage relative">
      <HomeBanner />
      <div className="container">
        <FieldSuggestion />
        <div className="homepage-main">
          <CourseSuggestion
            subHeaderName="精品推荐"
            main={courseSuggestion1()}
          />
          <CourseSuggestionWithBanner
            subHeaderName="编程入门"
            midConent={subHeaderOl}
            checkMore={checkMore}
            main={happyPython()}
            topBanner={topBanner}
            sideBanner={sideBanner}
          />
          <CourseSuggestionWithBanner
            subHeaderName="数据分析师"
            midConent={subHeaderOl}
            checkMore={checkMore}
            main={dataAnalyst()}
            topBanner={topBanner2}
            sideBanner={sideBanner2}
          />
          <CourseSuggestion
            subHeaderName="机器学习工程师"
            midConent={subHeaderOl}
            checkMore={checkMore}
            main={mearchineLearning()}
          />
          <CourseSuggestion
            subHeaderName="前端开发工程师"
            midConent={subHeaderOl}
            checkMore={checkMore}
            main={frontend()}
          />
        </div>
      </div>
    </div>
  );
};

export default Home;

它的结构,也是比较简单直接的:

|- Home
|  |- HomeBanner
|  |- container
|  |  |- FieldSuggestion
|  |  |- CourseSuggestion
|  |  |- CourseSuggestionWithBanner
|  |  |- CourseSuggestionWithBanner
|  |  |- CourseSuggestion
|  |  |- CourseSuggestion

从日常开发的角度而言,这样拆分的结构也算是比较合理。虽然说 subHeaderName 值都是写死的,但是如之前所言,大多数情况下这里可以来自于 constant 中的数据,十有八九都会从其他的地方动态地传送过来。

也就是说,但凡结构不会发生任何的变化,课程推荐中课程的变化,或是明天领域推荐从 编程入门 换成了 人工智能,这都不会涉及到代码层的变动。

这也就意味着不需要停止服务器去重新部署,从而增强了服务层的稳定性与可靠性。

最后,首页还剩下最后两个模块就写完了,一个是老师列表的渲染,另一个是左侧滑动部分的渲染。感兴趣的可以试试看自己做一下,应该说不是很难的。

老师列表的逻辑和课程列表的渲染很像,多出来的一点内容就是在鼠标悬浮时会显示出完整的信息。

如果能够完成首页的内容的话,也就说可以相对完成比较复杂的业务逻辑了——当然,这里指的只是 PC 端。

以上是关于[项目实战,源码完整]手把手教你怎么封装功能,React 重写学成在线 IV的主要内容,如果未能解决你的问题,请参考以下文章

手把手教你写项目

游戏开发实战手把手教你在Unity中使用lua实现红点系统(前缀树 | 数据结构 | 设计模式 | 算法 | 含工程源码)

游戏开发实战手把手教你在Unity中使用lua实现红点系统(前缀树 | 数据结构 | 设计模式 | 算法 | 含工程源码)

150行代码写个低配版WPS?:手把手教你实现+附完整源码

Pytthon实战------黑白老照片上色,手把手教你用Python怎么玩儿!

Linux——Linux驱动之设备树下platform总线驱动编写实战(手把手教你设备树下platform总线利用GPIO控制蜂鸣器完整实现过程)