乐趣区

关于react.js:如何设计实现H5营销页面搭建系统

背景

近几年,low codeno codepro code等越来越多的呈现在咱们的视线中。抱着不被卷的心态 🐶,我决定来深刻摸索一下。

我所在的是营销部门。每天 / 月都承载着大量的营销流动,本文也是我在摸索可视化搭建过程中的一些心得体会

其实这些名词都与 搭建 相干。其中一个利用最广的场景就是 营销。咱们晓得无论是淘宝、京东这些电商巨头,亦或是携程、去哪儿这些OTA,每天 APP 上都承接着有数的流动页面。

大抵梳理一下营销流动的一些特点:

  • 页面相似: 页面布局和业务逻辑较固定
  • 需要高频: 每周甚至每天有多个这种需要
  • 迭代疾速: 开发工夫短, 上线工夫紧
  • 开发耗时: 开发工作反复, 耗费各方的沟通工夫和人力

不同于惯例的业务开发,营销流动往往受影响的因素很多:节假日大促、政策规定等,所以往往可能是今天上午说的流动,今天就要上这种。如果单靠前端同学去保护,那怕不是要加有数的班(比方之前的我 😭)

每次来一个新流动,都靠前端同学去画页面,显然这种效率是极低的。如果排期拮据点还行,如果遇到 618 双 11怕不是要逼疯咱们。。

楼层搭建

鉴于这种场景,外部也进行了很多的探讨。得出的统一论断就是:开发同学提供营销搭建后盾,页面做成可配置化,配置的工作交给产品 / 经营同学。这样,基于 楼层 搭建营销页面的计划就应运而生了。

其实 楼层搭建 在营销页面的搭建中是一种比拟常见的形式。

如上图是京东的一个流动页面,页面次要由三局部组成:头图楼层、优惠卷楼层、热销楼层。因为就像生存中的盖楼一样,所以在晚期的营销搭建中,就有了 楼层 的概念。每个楼层其实就对应了一个具体的组件。

而后在具体楼层的 编辑内容 区域就能够去上传对应的数据了。

但这种形式有一个很大的毛病就是:不够直观。随着业务的疾速迭代,也陆续失去了一些反馈。最终发现经营同学真正须要的是那种能够间接拖拽生成页面的,也就是 可视化搭建

可视化搭建

楼层搭建 的根底上进一步革新为 可视化搭建 ,复杂度晋升了很多。单纯的去看页面的不同出现,可能仅仅就是加了一个 拖拽 的操作。但真正筹备去落地的时候,发现其中的细节特地多,也蕴含了很多的设计理念在外面。

咱们先来看一下原型图,而后仔细分析一下须要做的事件:

市面上大部分营销可视化搭建零碎根本都是相似上图这样的页面出现。左侧对应组件区域,两头是画布区域,右侧是属性区域。

大抵操作流程就是拖动左侧的组件到两头的画布,选中组件,右侧属性面板就会展现与该组件关联的属性。编辑右侧属性,画布中对应的组件款式就会同步更新。页面拼接实现,可通过相似 预览 的操作进行页面预览。预览无误,即可通过 公布 按钮进行流动的公布。

流程梳理完,咱们来看下我的项目的基础架构:

这里我基于原型对我的项目设计进行了性能的铺平,其实还是围绕 组件 画布 属性面板 这三块。

到这里,咱们思考几个问题:

  • 画布区域如何渲染已增加到画布中的组件(组件库组件会很多,画布中可能只需增加几个组件,思考如何做动静渲染)?
  • 组件从左侧拖入画布区域,选中组件,就可晓得该组件关联的属性。组件 Schema 如何设计?
  • 画布区域和预览时组件的渲染是否可共用一套渲染逻辑?
  • 组件的数据如何去保护(思考增加组件、删除组件、组件渲染 / 预览等场景)
  • 组件库如何保护(思考新增组件满足业务须要的场景)

首先来看第一条,简略演绎就是 动静加载组件

动静加载组件

如果你常常应用 vue,那我想你对vue 中的动静组件必定不生疏:

<!-- 当 currentView 扭转时组件就扭转 -->
<component :is="currentView"></component>

市面上的大部分编辑器也都是利用了这个个性,大抵实现思路就是:

  • 用一个数组 componentData 保护编辑器中的数据
  • 将组件拖动到画布中时,将此组件的数据 pushcomponentData
  • 编辑器遍历(v-for)组件数据componentData,将组件顺次渲染到画布中

因为我在的团队包含我本人始终都在应用 react,这里着重来提下react 组件动静加载的实现形式,框架应用的是umi

我在实现这部分性能时,在 umiapi中找到了 dynamic

封装一个异步组件:

const DynamicComponent = (type, componentsType) => {
  return dynamic({loader: async function () {const { default: Component} = await import(`@/libs/${componentsType}/${type}`
      );
      return (props) => {return <Component {...props} />;
      };
    },
  });
};

而后在调用的时候,将组件数组传入即可:

const Editor = memo((props) => {
  const {componentData,} = props;
  return (
    <div>
      {componentData.map((value) => (
        <div
          key={value.id}
        >
          <DynamicComponent {...value} />
        </div>
      ))}
    </div>
  );
});

解决了第一个问题,咱们来看第二个,也就是:组件 Schema 该如何设计

组件 Schema 设计

这里波及到组件、画布和属性区域三块的联动。次要蕴含组件强相干的表单属性以及初始值。

因为波及到组件属性的字段限度及校验,为了标准和防止出错,倡议我的项目应用 ts

这里以一个 TabList 组件为例,展现一下它的 Schema 构造:

const TabList = {
  formData: [
    {
      key: 'tabs',
      name: 'Tab 名称',
      type: 'TitleList',
    },
    {
      key: 'layout',
      name: '布局形式',
      type: 'Select',
      options: [
        {
          key: 'single',
          text: '单列',
        },
        {
          key: 'double',
          text: '双列',
        },
      ],
    },
    {
      key: 'activeColor',
      name: '激活色彩',
      type: 'Color',
    },
    {
      key: 'color',
      name: '文字色彩',
      type: 'Color',
    },
    {
      key: 'fontSize',
      name: '文字大小',
      type: 'Number',
    },
  ],
  initialData: {
    tabs: [
      {id: uuid(6),
        title: '华北',
        list: [
          {
            icon:
              '',
            goCity: '烟台',
            backCity: '北京',
            goDate: '08-18',
            goWeek: '周三',
            airline: '中国联结航空',
            price: 357,
            disCount: '4',
          },
        ],
      },
    ],
    layout: 'single',
    color: 'rgba(153,153,153,1)',
    activeColor: 'rgba(0,102,204,1)',
    fontSize: 16,
  },
};

在组件初始化时就约定好其对应的构造,当将组件拖入画布区域后,咱们能够拿到以后选中的组件数据,而后右侧的属性面板就能够渲染出对应的可编辑表单项。来看下右侧表单区域的代码:

const FormEditor = (props) => {const { formData, defaultValue} = props;
  console.log('FormEditor props', props);
  const [form] = Form.useForm();

  const handleFormChange = () => {console.log('表单更新',form.getFieldsValue());
  };

  return (
    <Form
      form={form}
      initialValues={defaultValue}
      onValuesChange={handleFormChange}
    >
      {formData.map((item, i) => {
        return (<React.Fragment key={i}>
            {item.type === 'Number' && (<Form.Item label={item.name} name={item.key}>
                <InputNumber max={item.range && item.range[1]} />
              </Form.Item>
            )}
            {item.type === 'Text' && (<Form.Item label={item.name} name={item.key}>
                <Input />
              </Form.Item>
            )}
            {item.type === 'TitleList' && (<Form.Item label={item.name} name={item.key}>
                <TitleList />
              </Form.Item>
            )}
            {item.type === 'Select' && (<Form.Item label={item.name} name={item.key}>
                <Select placeholder="请抉择">
                  {item.options.map((v: any, i: number) => {
                    return (<Option value={v.key} key={i}>
                        {v.text}
                      </Option>
                    );
                  })}
                </Select>
              </Form.Item>
            )}
          </React.Fragment>
        );
      })}
    </Form>
  );
};

表单区域具体表单项产生扭转后就会触发 onValuesChange,也就是ant design 表单的 字段值更新时触发回调事件 。这时数据就会更新到store 中。而画布的数据源就是 store 中的 componentData 进而页面会实时更新。来看下整体的数据流转图:


至此,第二个问题也就解决了。

接着看第三个问题:画布区域和预览时组件的渲染是否可共用一套渲染逻辑?

组件共享

咱们能够把预览组件了解为画布区的动态版本或者快照版本。从页面出现上来看并没有太大的差别,那么从代码设计上,这两局部当然就能够共享一个组件。咱们把这个共享组件叫做 RenderComponent.tsx,数据源为store 中的 componentData,而后联合DynamicComponent 组件,就失去了如下代码:

const RenderComponent = memo((props) => {
  const {componentData,} = props;
  return (
    <>
      {componentData.map((value) => (
        <div
          key={value.id}
        >
          <DynamicComponent {...value} />
        </div>
      ))}
    </>
  );
});

数据存储 / 散发

至于第四个问题:组件的数据如何去保护(思考增加组件、删除组件、组件渲染 / 预览等场景),其实在下面答复第二个问题的时候,曾经提到了。全局有保护一个store

state:{
  // 所有增加到画布中的组件数据
  componentData:[],
  // 以后编辑的组件数据
  curComponent: {}}

reducers:{
  // 增加组件到 componentData
  addComponentData(){},
  // 编辑组件,更新 componentData 及 curComponent
  editComponentData(){},
  // 删除组件
  delComponentData(){}
}

对于 可视化编辑器 这种大型前端我的项目,须有一个全局状态管理机制去做数据的存储和散发。这样对于数据状态的共享和同步也是很有帮忙的。

组件开发 / 保护

来看下面提到的最初一个问题:组件库如何保护(思考新增组件满足业务须要的场景)

这种目前有两种通用的做法:

  • 间接放在我的项目中
  • 抽成 npm 包,造成独立的第三方组件库

如果是我的项目初期,我感觉第一种做法也不是不能够,不便调试。但久远来看,营销场景下积淀进去的组件相对不会少,抽成第三方 npm 包才是理智的抉择,同时要配合一个相似组件治理后盾的管理系统,对组件做对立的治理。

回到组件自身而言,必须有严格的开发标准。每个组件原则上只是出现上的不同,对于约定俗成地组件研发标准则必须恪守。至于如何去限度,能够通过文档(弱)或者 cli(强)去做。

模板

除了下面的几个问题,还有一个点没提到:模板。咱们晓得营销流动有一个很典型的特点:页面相似。如果经营 / 产品同学从零去生成一个页面也是挺消耗工夫的,而且大部分流动都是归属于某一个大类上面的,咱们能够把这些类似的流动抽成模板。基于模板创立就会省时省力很多。鉴于这部分内容还在开发迁徙中,临时就不开展细说了。

到这里,我感觉曾经把 可视化编辑器 实现上最为简单的几局部以问题的模式一一解答了。其实无论是 组件动静加载 还是 组件 schema 的设计 数据结构的设计 组件库的保护 等,每个团队都能够制订一套适宜本人的标准,没有相对的对错之分。

其实在这个编辑器的实现过程中,有很多不容咱们疏忽的底层实现细节。包含:

  • 拖拽
  • 组件图层层级
  • 放大 / 放大
  • 撤销 / 重做
  • 吸附
  • 绑定事件 / 动画

这些细节我就不一一开展说了,举荐一篇文章:可视化拖拽组件库一些技术要点原理剖析。文章对于下面提到的技术要点都有很具体的阐明。

low code/no code/pro code

下面说了这么多,上面让咱们回到文章最开始提到的low code/no code/pro code。我会联合咱们的可视化编辑器来论述一下这三者。

首先来看下经营 / 开发同学应用编辑器创建活动的大抵流程:

no code

首先来简略阐明一下,什么是no code:从字面上来看就是无代码,也就是不写代码。

从下面的流程图中,能够看到经营 / 产品同学通过可视化编辑器,不必写一行代码,就能够搭建出功能齐全的流动页面。这种对应的就是no code

low code

low code的定义则是低代码、少写代码。

在下面的流程图中,更多体现在前端同学开发组件库。须要写局部代码,整体通过拖拽的形式生成的形式。对应的就是low code

pro code

pro code的定义是纯代码,也就是不通过任何可视化工具,全靠开发手写的代码模式。在 low codeno code呈现之前,这种形式是最为广泛的研发形式。

在下面的流程图中,这部分并没有体现。然而在理论的业务开发中,这种场景却是常常存在的。可能以后的一个营销流动,交互简单、链路长,那通过本文这种可视化编辑器是很难去定制的。只能通过开发去手动写代码的形式去满足业务需要。

可视化编辑器更多的是去满足规定相似的页面开发,首要职责是去加重反复业务的开发

瞻望

至此,一个营销零碎的搭建摸索演进流程我就大抵梳理结束了。

但,这只是一个开始。本文更多的是侧重于前端侧的摸索,也仅仅是向可视化编辑器迈出了第一步,只是一个更偏向于纯前端的我的项目,很多逻辑都还没有思考。这里列一下前面要做的吧:

  • 模板市场
  • 数据中心
  • 埋点
  • 组件调试 / 预览
  • 缓存
  • 凋谢 api 能力
  • CDN
  • 跨端

❤️ 爱心三连

1. 如果感觉这篇文章还不错,来个 分享、点赞、在看 三连吧,让更多的人也看到~

2. 关注公众号 前端森林,定期为你推送陈腐干货好文。

3. 非凡阶段,带好口罩,做好集体防护。

退出移动版