浅谈前端框架中的DSL

一、前言

一个普通的web网站应用使用 html、xhml 等更具描述能力的 external dsl(domain-specific language)来描述界面,然后使用javascript代码来解决界面上的一些逻辑问题,使用css来描绘界面的样式。这些 external dsl 用于将数据配置跟代码逻辑分离开来

一些现代语言加入了 internal dsl 这种东西,它赋予你在代码中写 dsl 的能力:比如jsx语法,vue语法。就是在javascript中使用类似于 html 的语法。

前端编程界的趋势是将 external dsl 混写 javascript 这类着重表达逻辑的语言里面。

分享一段flutter dart代码:

// 这段代码可以类比于`React.createElement`
class Drawer extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
        return Drawer(
            child: Column(
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: [
                DrawerHeader(
                    decoration: BoxDecoration(color: Colors.green),
                    child: Padding(
                    padding: const EdgeInsets.only(bottom: 20),
                    child: Icon(
                        Icons.account_circle,
                        color: Colors.green.shade800,
                        size: 96,
                    ),
                    ),
                ),
                // Long drawer contents are often segmented.
                Padding(
                    padding: const EdgeInsets.symmetric(horizontal: 16),
                    child: Divider(),
                ),
                ListTile(
                    leading: SettingsTab.androidIcon,
                    title: Text(SettingsTab.title),
                    onTap: () {
                    Navigator.pop(context);
                    Navigator.push<void>(context,
                        MaterialPageRoute(builder: (context) => SettingsTab()));
                    },
                ),
                ],
            ),
        )
    }
}

从上面的Dart代码可以看出,使用OO的编程语言去描述界面,会显得代码语义不够直观(请注意BoxDecoration, Padding的使用)。

因此,描述界面我们更倾向于专门使用描述性质的语言,描述性语言可拓展性好,对人类友好,结构清晰易读。比如xml,从这个角度来看待React组件中的render方法,和Vue组件中的<template></template>就发现一切都明了了起来。

虽然React,Vue中的 DSL 会被框架本身转化成javascript代码。但其 DSL 部分的内容,对开发者的更友好,开发心智负担减少

另外的思考:html,xml 语言从控制流的角度来看,无法写条件分支和循环分支。而Vue内置的v-for, v-if 关键字优雅的解决了这一问题。

二、从Vue看React

既然DSL语言描述界面有天然优势,我们更希望,在一个文件中,DSL的归DSL,OO的归OO。因此,对于如下的代码:

// Example.js
class Example extends component {
    this.state = { list: [1, 2, 3] }
    render() {
        <ul>{this.state.list.map(val => <li>{val}</li>)}</ul>
    }
}

// Example.vue
<template>
    <ul><li v-for="val in list">{{ val }}</ul>
</template>
<script>
    export default {
        data() {
            return {
                list: [1, 2, 3]
            }
        }
    }
</script>

我更倾向采用vue的写法。

但是react本身是没有开发自定义指令相关的功能。好在社区根据babel的转码es5的流程,自定义封装了相应的babel插件,以满足我们在react的render函数中使用类似于v-for, v-if 的指令功能

实际上,我们可以通过外包一层组件的方式来达到相似的目的。

1. if[Hidden]组件

/**
 * 对于返回多个节点的情况,请用`<template></template>`标签包裹即可
 */
const Hidden = (props) => {
    const { visible, children, render = null } = props
    const comp =
        children.type === 'template' ? children.props.children : children

    if (visible) {
        if (typeof render === 'function') {
            return render()
        } else {
            return comp
        }
    } else {
        return null
    }
}

2. Switch组件

const Switch = (props) => {
    const { value, children } = props
    return value ? children[0] : children[1]
}

3. Match组件

const Match = (props) => {
    const { exp, children, default } = props

    let content = default

    children.forEach((child) => {
      if (child.props.value === exp) content = child
    })

    if (content && content.type === 'template') content = content.props.children

    return content
}

最终的写法如下:

<Hidden visible={true}><div>this text should be display</div></Hidden>

三、参考链接

posted @ 2021-05-14 11:44  西河  阅读(158)  评论(0编辑  收藏  举报