如何使用 React/Angular/Vue 使 </div> 更接近其 <div>?

How to bring a </div> closer to its <div> using React/Angular/Vue?

序曲

经验丰富的 C(比方说)开发人员看到一个 200 行的函数会感到不安。他们将努力将其分为(比如说)十个功能,每个功能 20 行。笼统地说,objective 是一个永远不会跨越一个屏幕的功能。

上下文(更新)

如果桌面(比如 C 语言)应用程序是模块化编写的,则可以轻松定位错误。查看 main 调用的所有函数。找出罪魁祸首;看看里面;递归。相比之下 HTML 开发是一场噩梦。当发现缺陷或需要改进时,很难缩小应该修改的范围。是否有比服务器端模板或客户端库更好的模块化工具? React/Angular/Vue适合这份工作吗?怎么样?

动机

Web 开发人员面临同样的问题。开盘 <div> 和相应的闭盘 </div> 之间的线永远不要跨越一个屏幕是可取的。如果不能在一个屏幕上同时看到两者,就很难在脑海中保持良好的代码形象。

模板引擎,比如 Jinja,可用于拆分 HTML 文件。

例如,有一个文件

<div class="main">
    <div class="left">
        <!-- many lines -->
    </div>
    <div class="right">
        <!-- many lines -->
    </div>
</div>

Jinja 的推导可用于将 HTML 文件拆分为父文件和子文件。

<div class="main">
    {% block left %}
    {% endblock %}
    {% block right %}
    {% endblock %}
</div>

{% extends "main.html" %}
{% block left %}
    <!-- many lines -->
{% endblock %}
{% block right %}
    <!-- many lines -->
{% endblock %}

这让人想起在 C 语言中拆分函数的方式。长函数中丑陋的标志是缩进 变得过多,然后也很难准确地看到循环开始和结束的位置。

D3.js同样可以使用。上面的HTML文件变成一对HTML和JS文件

<div class="main">
</div>

let main = d3.select("main")
let left = main.append("div")
               .attr("class", "left");
let right = main.append("div")
                .attr("class", "right");

(或者,对于此 objective 的工业级 D3 使用,请参阅 here。)

问题

这些解决方案都不合适。他们喜欢使用一个强大的工具来做一些非常简单的事情,而代码的新手(或离开后的自己)不会清晰地看到一些几乎微不足道的东西。

如何使用 React/Angular/Vue 拆分上面的第一个 HTML 文件,以确保 <div class="main"> 和它的结尾 </div> 无论如何都只分开几行左右 DIV 中有多少代码?

我发现许多旧的和现代的框架都有同样的问题,它们没有考虑组件和组件重用。特别是,如果您想要包含数据元素,并假设您希望 left 是邮寄地址,right 是送货地址,那么创建像 [=13= 这样的通用组件是非常困难的] 同时用于同一表格中的两者,并稳健地处理数据。

您可以按顺序进行,例如对于 name="street" 的输入,数据将是一个数组,左侧可能是 [0],右侧可能是 [1],但是您必须小心处理空值,如果重新设计表单,数据可能不会到达更新操作的正确位置。

我知道它很古老,但我发现 ExtJS 是处理组件和处理数据的非常好的框架。相当于您的 HTML 可能是:

{
    name: 'main',
    xtype: 'left-and-right',
    leftItem: {
        name: 'mailing'
        xtype: 'address'
    },
    rightItem: {
        name: 'shipping',
        xtype: 'address'
    }
}

显然,传播需要更多的智慧name,但所有这些都是在幕后处理的,因此您可以参考mailing.streetshipping.street而不必担心关于空值和表单重新设计。

不幸的是,ExtJS 基本上是垂死的,现在由一家因各种错误原因而闻名的公司所有,所有相关开发人员都离开了,所以不建议尝试这条路。

Google Polymer 的方向是正确的,并且它与框架无关,因此它可能是与任何现代框架一起使用的不错选择。同样,如果您将其用于数据,则可能需要构建智能来传播 name.

你的问题很周到。我觉得我的回答有点简单,所以我可能误解了你提问的目的。

React 和 Vue 利用组件来组织模板并抽象出 DOM 的复杂性。您可以在这些组件中封装任意多的行为。

从 Vue POV(虽然 React 有类似的工具来实现相同的目标)......如果你想要更少的代码行,你可以封装整个 "left" 和 "right"(它们的行为、样式和模板)作为组件*。如果两个组件不共享任何状态或数据,这将变得非常简单明了,因为您不需要传递任何道具。


代码示例(在 Vue 中)

这是一个过于简单的示例,通常 MyPage.vue 会有其他行为,例如加载、动画或设置初始应用程序的数据。

// MyPage.vue
<template>
    <div>
         <v-left/>
         <v-right/>
    </div>
</template>

<script>
import VRight from '@/components/right.vue';
import VLeft from '@/components/left.vue';

export default {
    components: {
        VRight,
        VLeft
    }
}
</script>

最后,Vue 术语可能涵盖得太多了,但是 this document.

中有一些很好的代码组织示例(适用于任何语言或框架)

代码复杂性的衡量标准不仅在于给定函数中的代码行数,还在于实现某事时需要接触的地方数量或破坏代码其他区域的风险。

前端代码最脆弱的区域之一是 CSS,这就是为什么用组件打包 CSS(使用 CSS-in-JS 方法或使用 Vue 的作用域styles) 大大提高了您编写的代码的稳定性和可重用性。

希望对您有所帮助!!

*(在很多情况下这样做并不是最佳选择是有原因的)

"How can the first HTML file above be split using React/Angular/Vue to make sure that and its closing remain only a few lines apart no matter how much code goes into the left and right DIVs?"

JSX(XML 编译成创建虚拟 DOM 元素的 JS 代码的方言)很好地实现了这一点。它在 React 中使用,有时在 Vue 中使用。

以使用许多函数的 C 示例为基础,这正是我们在这里所做的。每个 JSX 标签代表一个 returns 另一个函数或一些 UI 元素的函数。我们可以将 <App /> 拆分为 <Main /><Left /><Right /> 函数,而不是将所有内容都放在 <App /> 中:

const App = props => (
  <Main>
    <Left />
    <Right />
  </Main>
)

ReactDOM.render('.app', <App />);

回答主要问题,这是这些框架存在的理由之一吗?恕我直言,不,它只是用于表达创建 vdom 元素的调用的 shorthand,因为编写 <App /> 比到处嵌套 React.creatElement({...etc}) 更容易。因为它恰好代表函数,所以它确实符合你在序言中提到的想法。但我认为它只是为了方便创建 vdom 而创建的 elements/HTML-in-JS,而不是明确作为模板引擎(其中有很多!)

如果我理解正确你的问题,我想你可以在使用 Svelte 之后得到你想要的东西,它与 React/Vue 有相似的目标,但在某些方面更像是一个组件编译器而不是一个框架。

官方 tutorial 上手非常快 read/play 并且教授几乎所有知识。

在你的情况下你可以看看这个playground:

App.svelte - 主应用程序文件

<script>
    import Main from './Main.svelte';
    import Left from './Left.svelte';
    import Right from './Right.svelte';
</script>

<Main>
    <Left />
    <Right />
</Main>

Main.svelte - <Main> 组件(<slot> 表示来自组件调用者的内容 - 在本例中它包含 "calls" 到 Left 和 Right)

<style>
...
</style>

<div class="main">
    <slot></slot>
</div>

Left.svelte - <Left> 分量

<script>
    import Box from './Box.svelte';
</script>

<Box title="Left">
    <p>
    ...
    </p>
</Box>

Right.svelte 本质上等于 Left.svelte

Box.svelte - <Left><Right>

都使用的公共 <Box> 内部组件
<script>
    export let title = "box";
</script>

<style>
    ...
</style>

<div class="box">
    <h3>
        {title}
    </h3>
    <slot></slot>
</div>

(这里把Left和Right共同的部分分解成一个Box组件,但是这显然是没有必要的)

为了完整起见,把例子变成一个完整的程序:

main.js

import App from './App.svelte';

const app = new App({
    target: document.body,
});

export default app;

package.json

{
  "name": "svelte-demo",
  "version": "1.0.0",
  "scripts": {
    "build": "rollup -c",
    "dev": "rollup -c -w",
    "start": "sirv public"
  },
  "devDependencies": {
    "rollup": "^1.12.0",
    "rollup-plugin-commonjs": "^10.0.0",
    "rollup-plugin-livereload": "^1.0.0",
    "rollup-plugin-node-resolve": "^5.2.0",
    "rollup-plugin-svelte": "^5.0.3",
    "rollup-plugin-terser": "^5.1.2",
    "svelte": "^3.0.0"
  },
  "dependencies": {
    "sirv-cli": "^0.4.4"
  }
}

rollup.config.js:

import svelte from 'rollup-plugin-svelte';
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import livereload from 'rollup-plugin-livereload';
import { terser } from 'rollup-plugin-terser';

const production = !process.env.ROLLUP_WATCH;

export default {
    input: 'src/main.js',
    output: {
        sourcemap: true,
        format: 'iife',
        name: 'app',
        file: 'public/build/bundle.js'
    },
    plugins: [
        svelte({
            // enable run-time checks when not in production
            dev: !production,
            // we'll extract any component CSS out into
            // a separate file — better for performance
            css: css => {
                css.write('public/build/bundle.css');
            }
        }),

        // If you have external dependencies installed from
        // npm, you'll most likely need these plugins. In
        // some cases you'll need additional configuration —
        // consult the documentation for details:
        // https://github.com/rollup/rollup-plugin-commonjs
        resolve({
            browser: true,
            dedupe: importee => importee === 'svelte' || importee.startsWith('svelte/')
        }),
        commonjs(),

        // In dev mode, call `npm run start` once
        // the bundle has been generated
        !production && serve(),

        // Watch the `public` directory and refresh the
        // browser on changes when not in production
        !production && livereload('public'),

        // If we're building for production (npm run build
        // instead of npm run dev), minify
        production && terser()
    ],
    watch: {
        clearScreen: false
    }
};

function serve() {
    let started = false;

    return {
        writeBundle() {
            if (!started) {
                started = true;

                require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], {
                    stdio: ['ignore', 'inherit', 'inherit'],
                    shell: true
                });
            }
        }
    };
}

然后:

npm install
npm run dev

(请注意,这实际上是官方 svelte templeate 使用 npx degit sveltejs/template my-svelte-project 创建的内容,如着陆页所述)

好消息是 Svelte 编译为标准 JavaScript 类,因此可以使用来自正则化 JavaScript 的组件。从这个角度来看,它是一个无框架框架。参见示例 this article