前端模块化开发实验(三)—— React 模块的Welcome world

光有 webpack 等bundling技术 是不够的,前面第一篇说了,构件表达,和构件复合方式 是推动 前端工程模块化的关键。因为即使你有打包不同web模块的机制,可是如果 模块本身的格式编写起来很费劲(看第二篇的Greeting V模块),可扩展性和可维护性差,那工程效率就很低,就谈不上成熟的工程模块化了。

前端开发上的革命性技术

业界在前端架构和实现技术上一直在努力,经历了一代jquery,二代backbone后,来到第三代的 React ,Vue,工程构件表达和复合方式有了质的提升,主要表现在以下三项革新技术:

  • 1 VDOM
  • 2 MVVM(reactivity)
  • 3 JSX (compostional)

前端应用程序构架中,V模块占有极其重要的位置,而直到第三代框架时,业界才发现它是天生 reactivity 的,这是一次思想革命;这个发现有一个渐进过程,二代框架“双向绑定”思想就是 reactivity 在技术上表现。更有趣的是 React 这个项目的名字揭示了 业界对 reactivity的发现。

第二个重思想革新是 HTML-IN-JS (JSX),“在JS里写 HTML”的JSX或 SFC 非常容易进行 构件表达和复合。当然React Vue所做远远不只这些。

这么说,webpack 提供了 前端模块化的最基础的打包技术,React Vue等 第三代 前端UI库 提供 精确(精确加高效) UI组件构件。

React Vue在 V 构件表达和复合上的革新

前面的纯JS版里,两个V模块的制作也是 有构件表达方式,和复合方式

  • 1 构件表达:JS对象,DOM对象,和ESM物理模块将 V相关逻辑组织在一起,例如外部CSS模块,交互事件处理方法
  • 2 复合方式:用append方法 拼接 DOM 节点

只是这样的构件表达和复合 纯手工痕迹非常明显,非常烦琐,效率低下,到第二代(backbone)时,我们开始使用 类,template和对象设计模式(例如发布订阅模式实现双向绑定)。使用类的利好很明显,它是一个代码模板,可以用来派生不同的具体的V组件实例。

这里不讨论 二代的发展,直接分析 三代React Vue在 V 构件表达和复合上的特点。

V 模块的逻辑形式

我们经常强调代码「可维护」性,可维护性包括代码开发和修正,因为无论哪种,我们都是在分析推理自己曾经的或是别人写的代码。可维护依赖于我们团队甚至社区 对 程序架构有清晰一致的见解,所谓mental model。

我现在直接用「V模块 」这个概念指代了前端架构中最核心的构件,但是 “V模块 逻辑具体是指什么,应该有什么内容”,社区还没有比较一致见解。造成此困难我想有二原因。第一,社区关于「软件架构」认识的不成熟;第二,大部分人都是从工程实践中推进 前端架构的认识的,工程实践可能混淆一些概念,例如 用prop派生组件,和lifecycle 定制组件 都是工程需要,而不是V 模块的逻辑形式内容。

这里简要的罗列出我对V模块逻辑形式的一些看法:

  • 第一, V 的很多内容都可选,但是UI呈现(交互输出)是必须的,所以,React 的 V,一定有DOM render(hook就是retutn JSX);Vue 的V 一定有 template定义;
  • 第二,交互状态(reacte state, vue data)虽然属于可选,但是它在开发常 用UI时,几乎没有不用到的。大部分交互功能 都是围绕着 交互状态 数据进行推理编程的;例如,面对一个需求分解UI时,首要分析的是 交互状态的使用。
  • 第三,交互事件的处理,交互操作 + 交互状态 = reactivity

2023 review:V有DOM,有reactivity 就像 promise 有 then方法 ,有包装async API的 exetutor

V 模块的工程特性

  • 1 prop 派生实例
  • 2 lifecycle method 进行创建时定制

V 模块的复合

用HTML本身的结构语法 表达 V模块复合的利好就太明显了,声明式创建V模块,可以看得见 层次化 复合。标签属性 还可以用于实现prop派生。

Welcome world应用的改造

为了演示 V 的reactivity,本文将用 React 改写 Welcome world应用,另外,交互功能改为一个计数器。

用 React 改造 Welcome world 实质就是用 React的V构件 去实现 layout 和 greeting 两个模块,并复合到一起。我们尝试分析一下这两个V的形式:

  • 第一,layout 没有交互状态,也没有交互事件;
  • 第二,greeting 有交互事件,原来也没有交互状态,现在为了演示 reactivity,增加一个状态,测试一下React 的 reactivity功能;
  • 第三,复合非常简单,父子;

现在我们对于V 有了不少思想准备,工作前还要有环境准备——安装和配置React 。OK,开始改造。

安装 React 作为项目源码依赖

React 框架有两个核心包 ReactDOM(web and native renderer) 和React,他们分别用来创建index模块和 程序 V 模块(UI组件树):

  • 1 index模块:ReactDOM 将根组件 ( App模块)接入(render) DOM
  • 2 App V模块 :组件树的根,由基于React库开发的 V模块(主要是引用 React.componet , React.createElemet)
$ npm i react-dom react

安装和配置 JSX 转译工具

安装React 源码依赖已经可以开始工作了,但是一般我们都会使用 JSX来方便 编写V 的 呈现部分,所以要配置安装webpack loader ,增加build step。

安装babel 插件(注意这里是 开发依赖 -D)

$ npm i @babel/preset-react -D

配置webpack loader 和 bable 插件

{
    test: /\.jsx$/,
    exclude: /(node_modules|bower_components)/,
    use: {
        loader: 'babel-loader',
        options: {
            presets: ['@babel/preset-react']
        }
    }
},

hello react V模块

index是应用的入口,我们先写一个 简单 hello react V模块 给他测试:

import React from 'react';

export default function hello_react(){
    return (<h1>hello react !!!</h1>);
}

注意import 了 React,使用JSX(保存文件为.jsx),和这是个hook。

改写 _index_模块

import ReactDOM from 'react-dom';
import hello_react from './hello-react.jsx'

const approot = document.querySelector('#root')
// approot.append(Container)
ReactDOM.render(hello_react(),approot);

运行测试通过:

Screenshot from 2022 04 12 21 00 55 (Greeting V模块)

"Thinking in React ","Thinking in state chanage!" ,可以正式开始写web应用的核心模块类型——V模块,并且是使用最新的模块化思想去分析,推理,将我们的需求转化为V模块。

如何将一个复杂的交互功能分解为数个V模块,每个V负责什么样的功能,再用什么方式组合起来,这将是前端开发中最大的任务模式空间了。例如如果有共享状态,用prop drilling , context API还是 Redux data store 实现;有没有交互状态?是简单的还是复合的?被谁操控,又会被谁使用(react to who)等等等。V模块(组件)开发模式经验是大话题,这里不讲。

import React from 'react'
import react_logo from './images/react.png'

export function GreetingV(prop){
    let [count, setData] = React.useState(0);
    let increase = () =>{
        setData(++count);
    }
    return (
        <div id="box" className="greeting">
            <div id="logobox" >
                <img alt="logo" src={react_logo} className="logo"/>
            </div>
            <h1>Welcome to {prop.name} {count} times!!</h1>
            <input/>
            <button onClick={increase}>Click me</button>
        </div>
    )
}

Layout V模块

import {GreetingV} from './GreetingV.jsx';
function Container(){
    return(
        <div className="centerbox"><GreetingV name="Web World"/></div>
    )
}

测试通过

Screenshot from 2022 04 14 00 43 42

小结

到此我们基本完成本实验的目标:完整的介绍现代前端工程模块化的样子,并有了一个支持React 开发的webpack 环境配置。下一篇我们补充介绍 Vue模块格式,由于Vue和React 只是格式和风格之差,都是属于三代技术,内容不会太多。至于,复杂V模块(模式经验)、路由,和状态管理等话题虽然重要,但已经超出了本实验范围。

本实验有几个知识关键点:

  • 第一,web模块的种类:V模块(样式模块,资源模块),index模块,其它模块(Router, DataProvide)
  • 第二,V模块的格式:JS格式模块,React, Vue 格式模块
  • 第三,源文件,和编译好的模块;
  • 第四,V模块的逻辑,和工程属性

最后看看我们的项目的模块结构,和文件系统结构:

index.html
    L style.css
    E logo.png
    E favorit.ico
    E bundle.js
        L src
            L index.js
                L App.js
                    L Router.js or DataProvider.js
                        L Layout.js
                            L layout.css
                        E Greeting.js
                            L react_logo.png
            E index.template.html

项目目录:

[keminlau@localhost modular-web-frontend-webpack-js]$ tree -L 3 -I node*
.
├── dist
│   ├── e28e1a7073c8fa14c1e1.png
│   ├── index.html
│   └── main.bundle.js
├── LICENSE
├── package.json
├── README.md
├── src
│   ├── App.js
│   ├── GreetingV.jsx
│   ├── hello-react.jsx
│   ├── images
│   │   ├── react.png
│   │   └── webpack.gif
│   ├── index.js
│   ├── index.template.html
│   ├── layout.css
│   └── LayoutV.jsx
└── webpack.config.js

源码

源码在 https://github.com/nakeman/modular-web-frontend-webpack-js/tree/react ,在react 分支上。

裸男
Nakeman.cn 2023 Build by Gatsby and Tailwind, Deploy on Netlify.