React进阶(五)模块化与组件化

React进阶(五)模块化与组件化_第1张图片
R N G

今天要分享的是模块化与组件化,两个非常重要,想要用好React, 就必须要理解的概念。当然,许多读者朋友肯定已经在很早以前就已经接触过这两个概念,不过是否已经真正理解了呢,我们可以借助下面两个问题考验一下自己:

  • 闭包与模块的关系是什么
  • 模块与组件之间的联系是什么

第一个问题是我在面试的时候必问的问题之一,这个问题在很大程度上能够反映出来对方的JS基础掌握得是否扎实。不过如果大家读过我之前写的《前端基础进阶》,那么这个问题应该是没有什么难度的。

当我在面试中问闭包时,大家或多或少都能够分享一些闭包的概念,但是进一步再问闭包在实践中的应用时,大多数人都不知道应该怎么说。

在我以前的文章里有很详细的描述,如果你对闭包的基础概念还不清楚,建议回过头去补充一些知识

模块化的概念由来已久,并且在JS中也有很长久的使用历史。通常我们在编写代码时,会将复杂的问题根据实际情况进行合理的拆分,让代码更具备可读性与可维护性。因此一个模块可以理解为整体的一部分。而且随着JS应用复杂度的提高,模块化的应用也变成了必须。

在之前的JS中,没有专门为模块化提供相应的语法支持,但好在我们还有闭包。因此以前我们借助自执行函数来模拟一个模块。

var moduleDemo = (function() {
  function bar() {}
  function foo() {}
  function map() {}

  return {
    bar: bar,
    foo: foo,
    map: map
  }
})();

// 访问模块内部的方法
moduleDemo.bar();

bar,foo,map三个方法在函数内部被定义,但是却可以在外部使用。所以很简单就能看出,我们借助闭包实现了模块。借助这样的思路,我们可以封装一些工具方法组成一个单独的工具模块,以避免代码的重复编写。这样的比较出名的实践有 lodash, axios等。他们都是在实践中用得比较多的工具模块。

还可以看出,模块化其实也是单例模式的一种实践应用

接下来我们要思考一个小小的实践。使用原生的JS与html实现一个简单的选项卡。不知道大家脑袋里是否已经有了具体的方案。

在上一章中,我们介绍了create-react-app,借助此工具,我会把这系列文章中所有涉及到的案例与实践都集成在一个项目中,该项目的地址为 https://github.com/yangbo5207/react-advance。

但是很显然,我们这么多的demo,想要组合在一起,还是比较具备很强的复杂度,因此create-react-app提供的默认配置无法满足我们的开发与学习的需要,所以要在默认配置的基础上,进行一些改造与扩展,并且随着学习的深入,这套构建工具将会组件新增更多的能力,这里也无需大家就一定要去深入学习webpack,我会将构建工具的改动历史记录在 https://www.jianshu.com/p/0f56250a5f2b。我不会细说我为什么要这样改动以及各种原理,只会简单记录操作,以供大家在深入学习webpack时做参考使用。不过也建议大家跟着我的改动操作一次,这对于理解组件化会有很大的帮助,后续的文章,也要求大家至少对我做了那些改动有一个了解,不然可能会在某些描述上你会搞不清楚。

OK,多的不说,为了满足这篇文章的需要,我暂时将我们的构建工具新增了多页面构建。以后文章中的每一个demo,都会是一个单独的页面。demo的命名会类似于RA5_01.html, RA5_01.js, RA5_01.css

  • RA 表示react-advance 的缩写
  • 5 表示系列第五章
  • 01, 02, 03 表示该章demo的序列

构建工具初步改造完成之后,目录结构大致如下:

React进阶(五)模块化与组件化_第2张图片
目录结构

所有页面的入口html都放在public目录中。
所有页面的入口js都放在src目录中。

每一个页面的html文件与js文件的命名必须保持一致,例如RA5_01.html, RA5_01.js,这个规则由构建工具制定。

在public中新建RA5_01.html,写入一下简单代码:




  
  
  
  
  
  使用原生方案实现选项卡



  
标题1
标题2
标题3
内容1
内容2
内容3

暂时先找个目录src/styles用以存放css文件,新建一个RA5_01.css文件,将布局写好。

body {
  margin: 0;
}

html, body {
  height: 100%;
}

#root {
  width: 300px;
  margin: 20px auto;
  border: 1px solid #CCC;
  height: 400px;
}

.titles {
  display: flex;
  height: 44px;
  border-bottom: 1px solid #CCC;
}

.titles .item {
  flex: 1;
  height: 100%;
  text-align: center;
  line-height: 44px;
  font-size: 12px;
}

.titles .item.active {
  background-color: orange;
  color: #FFF;
}

.contents {
  position: relative;
  height: 100%;
}

.contents .item {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  display: none;
  align-items: center;
  justify-content: center;
}

.contents .item.active {
  display: flex;
}
React进阶(五)模块化与组件化_第3张图片
样式

最后在src目录中新建RA5_01.js

import './styles/RA5_01.css';

const titles = document.querySelector('.titles');
const contents = document.querySelector('.contents');

let index = 0;

titles.onclick = (event) => {
  const activeTitle =  event.target;
  const aindex = Number(activeTitle.dataset.index);
  
  if (aindex !== index) {
    titles.children[index].classList.remove('active');
    contents.children[index].classList.remove('active');
    
    activeTitle.classList.add('active');
    contents.children[aindex].classList.add('active');
    index = aindex;
  }
}

这样,一个简单的选项卡功能就实现了。

相信这对于大家来说没有任何难度。不过我们来接着思考一个问题。我们知道通常在一个页面,选项卡的应用其实很广泛,如果每次用都这样写一次,就感觉很麻烦,因此我们最好能够将选项卡封装成为一个模块,那么我们下次用到的时候,就直接引入模块就OK了可不可以呢?

我们用import引入的css文件,其实就已经被构建工具当成了一个模块来处理。

src/utils目录下创建模块RA05_tab.js

export const createTab = (containerElement) => {
  const titles = containerElement.querySelector('.titles');
  const contents = containerElement.querySelector('.contents');

  let index = 0;

  titles.onclick = (event) => {
    const activeTitle = event.target;
    const aindex = Number(activeTitle.dataset.index);

    if (aindex !== index) {
      titles.children[index].classList.remove('active');
      contents.children[index].classList.remove('active');

      activeTitle.classList.add('active');
      contents.children[aindex].classList.add('active');
      index = aindex;
    }
  }
}

我们可以看出,该模块提供了一个创建tab的方法createTab。这样,我们就可以修改RA5_01.js,直接引入该方法创建tab了。

import './styles/RA5_01.css';

// 引入模块时可省略.js后缀
// utils是在构建工具中配置了别名,因此不用使用相对路径来引入,构建工具会自动识别
import { createTab } from 'utils/RA5_tab';

createTab(document.querySelector('#root'));

请一定确保自己对ES6的语法已经基本掌握,否则后续的文章可能会有一些难度。

是不是使用起来就简单了很多。

那么现在大家应该对模块的概念应该比较清晰了。在webpack创建的构建工具中,认为一切文件都可以是一个单独的模块。一个js文件,一个css文件,甚至一张图片,只需要进行对应的配置,都是模块,可以使用ES6的 Modules语法引入。

当然思考并没有结束。想一想当我们继续要创建第二个新的tab时,我们要做一些什么操作?

  • html中要新增一段符合要求的html
  • 引入对应的css模块
  • 引入对应的js模块

麻烦的地方就在这里,每次创建新的tab需要执行很多操作,特别是html要整一段新的,就很容易出错,时间久了忘记了就不知道应该怎么用了。如果我们还需要引入图片什么的就更麻烦了。

那么我们能不能只引入一个完整的东西,就能够直接创建新的tab呢?当然是可以的,这就是我们接下来要明白的另一个概念:组件。

在React中提供了组件化的思路,结合webpack,我们可以很完美的创建一个组件,并且在使用时只需要引入一次就可以了,和上面的方式相比,想一想都觉得方便了很多。

我们来试一下:

在public中创建 RA5_02.html





  
  
  
  
  
  选项卡组件



  

src/pages中创建一个Tab组件,该组件将会由css和一段包含html模板(jsx)的js文件组件,因为我们暂时还没有学习到React的具体语法,因此暂时只需要感受一些这种方式即可。

/* src/pages/RA5_02/style.css */
body {
  margin: 0;
}

html, body {
  height: 100%;
}

#root {
  width: 300px;
  margin: 20px auto;
  border: 1px solid #CCC;
  height: 400px;
}

.titles {
  display: flex;
  height: 44px;
  border-bottom: 1px solid #CCC;
}

.titles .item {
  flex: 1;
  height: 100%;
  text-align: center;
  line-height: 44px;
  font-size: 12px;
}

.titles .item.active {
  background-color: orange;
  color: #FFF;
}

.contents {
  position: relative;
  height: 350px;
}

.contents .item {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  display: none;
  align-items: center;
  justify-content: center;
}

.contents .item.active {
  display: flex;
}
/* src/pages/RA5_02/index.js */
import React, { Component } from 'react';
import './style.css';

const defaultTabs = [{
  title: 'tab1',
  content: 'tab1'
}, {
  title: 'tab2',
  content: 'tab2'
}, {
  title: 'tab3',
  content: 'tab3'
}]

class Tab extends Component {
  state = {
    index: 0
  }

  static defaultProps = {
    tabs: defaultTabs
  }

  switchTab = (index) => {
    this.setState({
      index
    })
  }

  render() {
    const { tabs } = this.props;
    const { index } = this.state;

    return (
      
{tabs.map((tab, m) => (
this.switchTab(m)} > {tab.title}
))}
{tabs.map((tab, n) => (
{tab.content}
))}
); } } export default Tab;

这里我们自定义了一个Tab组件,在使用时,只需要引入该组件即可。

/* src/RA5_02.js */
import React from 'react';
import ReactDOM from 'react-dom';
import Tab from 'pages/RA5_02';

ReactDOM.render(, document.getElementById('root'));

使用React相关的APIReactDOM.render将Tab组件渲染进DOM结构中。

  • 这里的Tab就是一个Tab组件
  • 引入之后,就可以在jsx模板中直接跟html标签一样使用,
  • 在使用时,我们不再去关注html,css,js逻辑具体怎么实现,只需要关注如何引入,需要传入什么参数即可

由于前端页面的特殊性,页面上的一个元素,往往并不是由一个单一体,往往至少包含了html片段,css样式,js逻辑,或者图片等更多元素。因此组件化的概念非常适合前端开发,这也是React提倡的开发思路之一。

如果善于总结的同学,读到这里,就可以看出,组件化其实是模块化思路的一个延伸,一个组件由许多不同的模块组成。得益于React与webpack的发展,让组件化的思维可以实现并在目前的前端开发中大展拳脚,这也正是我们需要学习的开发思维之一。

基础概念的理解我想并不会太难,但是考验一个前端开发的功底的是,你是否能够合理的对一个页面进行组件划分。希望大家能够在以后的学习与实践中,不停的去思考这个问题,锻炼自己这方面的能力,合理的划分意味着你的代码具备更高的可读性,可维护性,以及更高的性能,这些都是评判你的代码是否优秀的重要标准。而这些,都是慢慢沉淀出来的。

你可能感兴趣的:(React进阶(五)模块化与组件化)