前语

闪电智能渠道,我从2018年开端接触这个项目就一向随同着它一路生长,从一个简略的案牍生成渠道到现在的智能内容创造,见证了这个渠道从功用简略到越来越强壮的整个进程。期间进行了屡次的项目重构和整合,经历屡次的产品升级,改进事务需求,逐渐构成现在比较完善的闪电智能内容创造渠道。

它的前身是莎士比亚体系,首要进行智能案牍创造;一起还有个李白体系,它首要是案牍创造,可Q X n 7 a e Z _ z是它是偏文艺范诗词类。

在莎士比亚线上运行) = ? _ D m + Y了一段时刻之后,莎士比亚和李白进行了整合,诞生了现在的闪^ 7 { E w p E ; *电智能内容创造p k ,渠道。

1. 闪; 9 @ 0 y m电介绍

闪电是闪电智能内容创造渠道的简称,它是 AI 智能生成渠道。g I [ L w + 5 xp % , : R持用户登录网 i [ V | R 站运用,也支持接口合作形式。针对类目未掩盖、内容类型拓展A n d ~等个性化需求,支持商家进行邮件提报等功用。首要包含N m 9以下五大模块:智能写作、智能视频、智能图片、智能商详、会员体系。

入口地址:aisd.jd.com

闪电智能创作平台项目前端总结

闪电渠道推出的时分新增了视频模块,有了榜首版o . k的视频生成和视频修改,在一年多时刻里的屡次版别迭代,新增了会员体系、智能商详、智能图片、闪电实验室等模块。

= S / 2 G R在的闪电智能内容创造渠道功用越来越强壮,用户数量在许多添加,它的价值正在不断攀升。

闪电智能创作平台项目前端总结

整个项目历程~ _ h g e Z / 6 @中,最难最不常见的是智能视频这个模块,也是投入时刻和E r i A精力最H 0 V n多的部分。为了更便利的生成视频,更友好J F % H @的视频修改体会,先后经历了两次大的改版,做了视频二期、视频三期。

投入这么多,它的收益也是很显着的。

视频模C [ [ . f块给了n X y + , y { L用户更多自由发挥的空间,得到越来越多商家用户的认可,它给商家用户创造的价值S T : j N s i是十分巨大的,产M # 4 u出的创造内容越来越多的体现在产品介绍上,或许您浏览的某些产品的短视频介绍便是闪电出品呢!

闪电智能创作平台项目前端总结

智能视频是最杂乱最具特征u l _ v E 的模块,作为前端开发者有必要要分享一下我的心路历程Q = } ( v h x x (,本文就首要依据这个模块谈谈开发时分的一些感受/ } A V和对前端开发的一些了解。

2. 一个详细事务场景引发的考虑

咱们接触的项目大多与电商或许 OA 有关,而闪电是个比较特其他存在,首要体现在智能视频这个模块,从视频生成到视频修改都有着相当杂乱的逻辑和交互。

先来看下这个视频生成的进程。

闪电智能创作平台项目前端总结

这不挺简略的嘛,我拿到这个需求的榜4 i O z首反应便是这样。L _ I 6

着手 codinT 2 n 3 – i P s `g 前先来整理一下需求流程:

闪电智能创作平台项目前端总结

这张流程图中是生成视频的首要流程,还有许多细节的逻辑没有在这儿边表达r ~ M G k E出来,这跟拿到需求的z N } o M & p榜首反应相差有点大啊!

整个流程下来就一个提交操作,完结这h 9 z M $些需求,有以下难点:

  1. 查验完 sku 的品类特点,需求无感知的切换选中模板
  2. n M q ~ , b程一和进程二之间来回切换需求保证流通并缓存已添加的内容
  3. sku 的添加和资料添加的不同顺序,正向和反向建立 sku 和资料的对应关系
  4. 相对杂乱逻辑和交互下到达开发高效、功用高效

有难点就要处理难点,知难而进: s & x 3 – ],先不要乱了阵脚。

为了可以高效的完结 coding,咱们总是会不断的寻找高效处理计划。在前端开发中,比方通过相同的类名设置一致的! Y v t u X s款式,相同的 DOM 结构完结类似的布局,函数的封装完结相同F h b l = [或类似的逻_ T R辑,等等方法来进步咱们的功率。

为了更高的开发功率,对上面的几种功率进步方法进行整合,逐渐构成组件化思维,并在项目和结构中运用。

现在的干流结构 React、Vue 都很好的完结了组件化,组件化也是随同前端开展不可或缺p D v z ? T * 1的规划思维,已然 React 具有组件化的天然优势那就运用起来!刚好本项目运I 3 S ] 2 ; H用了 React 技能栈。

先剖析一下页面结构和功用,建立起代码结o – Y ^ ( x F ;构再进行 coding 。

3. 将需求进行组件化,进步复用性和保护性

剖析页面结构和功用是为组件化做预备,那么先了解一+ V + . ( ] m f e下组件化的根本常识。

3.1 组件和组件化的认知

以前咱们经常会这样做,运用相同结构的 HTML 结构,4 j K w B G界说相同的类,然后写一套 CSS 款式,来到达相同或类似区块的款式结构复用,这在一定程度上有些类似组件的概念,可是这种方法最大的问题在于他们的逻辑并不是彼此独立。那么究竟什么是组件化呢q e @ . 4 G O % +

简略来说,组件便是将一段UI款式和其对应的功用作为独立的全体去看待,不B g f管这个全体放在哪里去运用,它都具有相同的功用和款式。

将组件完结复用的这种全体化细想便是组件化。不难看出,组件化规划便是为了添加复用性,灵活性,进步体系规划,然后进步开发功率。

3.2 组件化的大致演化进程

前端最初的形式f = Y H Y是频频的操作 DOM,发送恳求,改写数据,页面部分改写,是一种典型进程式的开发,开发体会式是 a F { ^很差的。

后来逐渐改进,学习后端思维,开端流行 MV* 形式,比方 MVC ,按事务逻辑,UI,功用,区分成不同的文件,结构明晰,规划明晰,开u R / * $ ` t C发起来也不错。

在这个基础上,又有了愈加不错的 MVVM 结构,它的出现,愈加简化了前端操作,并将前端的 UI 赋予了实在含义:你所7 f ; v w h [看到的任何 UI,应该都对应其相应的 ViewModel ,即你看到的 View 便是实在的数据,并且完结了双向绑定,只需 UI 改动,U; X = ^ FI 所对应的数据也改动,反之亦然。这的确2 / B很便利,但大部分的 MVVM 结 ] n w ~构,并没有完结组件化,或许说没有很好的完结组件化,由于 MVVM 最大的问题便是:

​ 1. 履行功率,只需数据改动,它下面一切监测数据上绑定的. S p h x UI 一般都会去更新,功率很低,假如你操作频频,很或许会十分频频的更新 UQ p b %I ,形成功用问题。

​ 2. 由于 MVVM 一般需求严厉的 ViewModel 的d H e 9 V K作用域,因而大部分状况不支持屡c f Q c g K次绑定,或许只允许绑定一个根节点做为顶层DOM烘托,这就给组件化带来了困难(不能独立的去绑定部分UI)。

而后,在此基础上,一些新的前端结构“取其精华,去其糟粕”{ w * l ],开端大力` l ! Y 9 A p (推广前端组件化的开发方法v z L S W l ; b, React 和 Vue 则是先行者。

就 React 而言,它是单向数据流办! l 4理规划的先驱,React + Rz @ , n / g n _ hedux 将 MVC 做到了极致g @ 9 h 7 X。下面请看一个实例。

3.3 组件化进程的亲身经历

React 供给了很好的组件化开发环境,以生成视频的进程进行页面和功用剖析,逐渐完结组件化。

3.3.1 页面剖析,块级区分页面结构

闪电智能创作平台项目前端总结

依据页面内容,可Y { Q ] z r 4 @以分为上下两部分,上方是两个操作进程,下方是主按钮。

进程一的模板挑选区分一个部分,进程二中每个进程区分为一个部分。进程二中的每个部分是有类似之处的,即都分为左侧的标题和右侧的内容区域,这是可以复g t D W用一些布局的Q g $

依据此番剖析,咱们根本的页面结构就有了。

<div class="video-index_main">
<div class="video-main">
<div class="tabs-nav"></div>
<div class="tabs-body">
<divx R ) w { E t u class="upload-item"L a *>添加产品:</div>
<div class="upload-item">添加资料:</divc U 4 x>
<u 1 } . P 2  g;div class="upload-item">视频尺度:</div>
<div class="upload-item">是否上传:</div>
</div>
</div>
<div class="button-container">生成视频</div>
</div>

首要的页面结构根本是这样,可是看到上面的功用截图,这每一个块 div 中的内容仍然是许多的,假如都在这一个文件中进行开发,将会显得十分冗杂,试想一下 Re) I 2 5 T m uact 组件的 r{ K m ender 中 jsx 代码几百上千行,想想或许都觉得头大吧,其他为完结各种交互需求保护的状况也将十分多,还有最重要的问题,部分的某个交互更新 state 也将G = m h & + % 6从头烘k x 6 = 3托整个组件,这将是十分大的O K c @ 3 e O功用耗费。

3.3.2 功用区分,按功用拆分页面

接下来依照功用块进D k b行拆分。

<div class="video-index_main">
<Tabs>
<TabPanel label="进程一:挑选模9 6 a板">
<Templates/>
</TabPanel>
<TabPy h Z 1 % ? Eanel label="进程二:添加信息">
<UA J R I u $ { x HploadMm N Z 5aterial>
<SkuInput />
<Maty o 4  b Perial />
<Aspect />
<Upload />
</UploadMaterial>
</TabPanel>
</Tabs&R : s ) H U M pgt;
<div class="button-cd C J : b Nontainer">生成视频</div>
</s ` J L ` - V ,div>

这样将进程一的内容独立成一个文件,进程二独立成一个文件,进程二中的每个部分再拆分成独立的文V b : V v件,就有了上面的结构, Tabs 则是一个组件。这样将具有比较杂乱的交互r M x . f . B D操作的块彼此分隔,整个页面结构也愈加明晰了。

3.3.3 笼统组件,K ) t 8将可复用的功用块组件化

再来看下组件化,仔细观察不难发现一些k h ~ { V o D u s类似的结构和功用是可以复用的。

闪电智能创作平台项目前端总结
闪电智能创作平台项目前端总结

看上面这两张图不难发现,有一些类似之处,整个外层是5 Q ` Q一个大的 Tab,挑选模板中仍然是一个 Tab,而添加资料这儿边仍然是一个 Tab,仅仅长的款式上些Q 7 6 : # % | F微有些差别算了,可是功用上却是如此的一起。

再来看添加产品 sku 这块当 sku 数量角度的时分需求可以左右滚动,添加资料中 sku 对应的一组资料相同有这样的需求,并且其他页面也有这样的需求。

依据此( E C Y b m @ D这块内容至少可以笼统出两个通用组件 Tabs、 HorizontalScroll 。进程二又可以笼统出 AddItem 组件。

<div clasM y @ 1 # Ls="video-index_main">
<Tabs>
<Tabs.TabPanel label="进程一:挑选模板">
<Templates />
</Tabs.TabPanel>
<Tabs.TabPanel labelF . h D w M 7 R )="进程二:添加信息"&z H Tgt;
<AddItem title="添加产品:" cls="add-sku"><SkuInput /></AddItem>
<AddItem title="添加资料:" cls="add-material"><Material /></AddItem>
<AddItem title="视频尺度:" cls="aspect"><Aspect /></A^ Z ) JddItem&gx 9 j n i ( ?t;
<AddItem title="是否上传:" cls="deploy"&R Q ( n I i  z Pgt;<Deploy />&l ~ c : 8 Yta g [ t;/Adu S N 9dItem>
</Tabs.TabPanel>
</Tabs>
<div class="button-container">生成视频</div>
</div>

AddItem 组件右侧的内容和布局并不相同,这并不是组件本身的差异,因而给出自R = ? 5 F o _ T界说的 class 入口,可依据 class 重写相应的款式。

Tabs 组件前后用到了三次,嵌套运用,这样只需C # f f v /求切换的交互逻辑只需求处理一次,只需求对款式的不同进行掩盖。HorizontalScroll 组件相同如此,在封装好组件之后将不在重视他的功用,直接运用即可,这在屡o { | @ u f次重* l – P F . # e复用到的场景将变的十分功率u ? X `

这儿仅仅列c J i S & 举了比较大的组件的区分,里边还有一些小组件的区分,比方每个 sku 的区分,每个资料的区分,这些小组件区分0 p t x ( q ^ : u的含义在于,它的状况只需求保护在它自己的文件中,组件的更新也只会影响它的这个组件返回,将会大大削减父级组件的从头烘托。这也是功用优化的一种手法,即下降组件从头烘托的次数和规模。

已然组件拆分! E t ! v W ] S可以为咱k 1 T f Q N b们带来这么多的优点,那就F v v / % a e P d把页面上的内容悉数拆分成大巨细小的组件吧!这样真的好吗?

3.3.4 组件拆分粒度,权衡组件拆分利害

最近经常和搭档讨论起组件化拆分的利害问题,部分人以为组件化会导致代码涣散,w / L Q ! B 9可读性和可保护性变得比较差,数据传递变得杂乱;部分人则以为组件化可以使代码结构明晰、模块区分清晰、可以进步{ . H _ A d n页面烘托功用。所以怎样挑选呢?

这是个值得沉思的问题,拆还是不拆这是个问题…

通过项目实践我以为,拆分组件的意图无非以下几个:

  1. 进步复用率,防止代码和逻辑臃肿
  2. 模块区分,可依据页面结构模块化开发
  3. 进步功用,细化组件可减小状况 0 & 7 5 x =改动引起的从头烘托的规模

依据以上意图再进行组件拆分,或许I T l ( C k @ Z M会有不相同的成果。再合作 redux 之类的数据办理,功用进` a w + k步将会愈加显着。

个人以为在一个大型前端项目中,这种组件化的笼统规划是很重要的,不仅添加了复用性进步了作业功率,从某种程度上来说也反应了程序员对事务和产品规划的了解,一旦有问题或许需求功用扩展时,或许许多之前的组件就能派上用场然后节省开发工时。

闪电智能创作平台项目前端总结

页面剖析和组件笼统也相同适用于视频修改页,详细的组件化完结就不多说了,可是这个页面数据流愈加杂乱。

页面组件化仅仅仅仅完结了开发的开端作业,很顺利,很a z O ` , N P R丝滑,接下来就开端事务数据开发吧。

React 是单向数据流办理,那么组件之间如何通信将是个十分重要的问题。这或许也是有些时分不愿q Z G K M T B ]意拆分组件的重要原因。

4. 将数据独立办理,从事务逻辑中剥离

上面提到组件化开发中最重要的问题是数据流的问题,当咱们把一个模块拆分成多 = s k 1 Z q 7 *个组件的时分,数据或许就会涣散到各个组件中了,一起由于 React 单向数据流的特性,数据只能由上至( # r d + _ D下传递,也便是只能从父组件传往子组件,那么更欠好处理的问题是同级兄弟组件之间的数据通信,以及多层嵌套下的 Prop 传递。

4.1 单向数据流的作业形式~ ` B

假如仅仅用 React 本身的 prop8 1 7s 或许 state 开发将变得十分厌恶,许多层级的 props 传递很简略混淆和丢失,一起根组件的功用耗费也是十分大的,兄弟组件之间的数据通信也J # / Y K需求通过频频的回调来处理。

闪电智能创作平台项目前端总结

就像上面这样,看似组件拆分了,实则数据都依赖于顶层,并且需求逐级向下传递,当 props 产生改动需求 re-render 的时分也会变得难以处理,是否还记得 componentWillReceivePropsshh ; 8 3 ~ mouldComponentUpdate 这些 API ,它们便是用来处理这种难题的,可是运用不当也会形成更严重的问题。

4.2 状况进步的作业形式

状况进步,同级组件中一起运用的状况需求一致保护在它们的父级组件中,然后组件一起订G k Y V * , ]阅,^ – ; y要想更改状况,需求在子组? r / y N #件中去修改组件中R o _ i Q的状况,一起其他子组件从头获取状况从头 render 。

闪电智能创作平台项目前端总结

这是最简略的状况进步,当嵌套层级比较深的时分,这个进程将变得十分冗杂,每一层组件都将传递许多的 props 特点和回调函数,越往下越多,很简略就紊乱了。Y ] ( W X I ; e R

4.3 状& C n N况同享的作业形式

在 React V16.3 之前虽然有官方供给的 context API ,可是存在一个问题:看似跨组件,实则还是逐级传递,假如中心组件运用了 ShouldComponentUpdate 检测` D B 7 A u到当前 state 和 props 没有改动,return fw p Ualse,那么 context 就会无法透传,因而 cont} H & ;ext 没有被官方推荐运用。

React V16.3 之后,新版的 context} 1 ~ * ! 6 t 处理了之前的问题,可以轻松完结,但仍然存在一个问题,1 L R / Jcontext 也是将底部子组件的状[ c M Y _ V况操控交给到了顶级组件,可是顶级组件状况更新的时分一定会触发一切子组件的 re-render ,也会带M / D来损耗。虽然可以通过一些手法来削减重绘,比方在中心组件的 ShouldComponentUpdate 里进行一些判别,可是当项目较大时,需求花许多的精力去做这件事。

闪电智能创作平台项目前端总结

数据流的问题,状况同享的问题似乎现已得到了处理,但总觉得不得劲。

当某个组件的事务逻辑十分杂乱时,代码会越写越多,由于只{ * w W w U T能在组件内部去操控数据流,没方法抽离,MoL L Q u 1 c ~del 和 View 都放在了 View 层,整个组件显得臃肿不堪,事务逻辑统统堆在一块,难以保护,看的人头大。

当数据流紊乱时,咱们一个履行动作或许会触发一系列的 setState ,假如可以让整个数据流变得可“监控”,甚至可以更详尽地去* ; /操控每一步数; B – | x据或状况的改动,那将会是一件十分棒的事情。

React 本身并未供给多种处理异步数据流办理的计划,仅用一个 setState 很难满足一些杂乱的异步流场景;

总结下来,运用 React 数据流仍然存在以下问题:

  1. 组件臃肿
  2. 状况变得难以预F ~ P # D U J % U知,难以回溯
  3. 异步数据流难以处理

4.4 运用数据流办理工7 B f l具,处理数据流形成的问题

为处理以上问题,需求用到数据流办理工2 ~ d ^ E {具来帮助完结,希望通过数据流办理工具可以将数据从 React 组件中脱离出来,只负责办理数据,让 React 专心于 View 层的制作,这样咱们的组件也将变得干净,区分愈加清晰。

数据流办理工具世面上有许多,比方 redux、mobx、dva等,他们都能和 ReacC % {t 很好的结合,这儿就不多做介绍了。本次以 ren t _ &dux 作为4 z % [ F R c数据流办理工具来阐明一下处理上述问题的思路。

  1. store: 供给一个全局的 store ,用来存储从组件中抽离出来的状况
  2. action:一个目标,用] 7 G x h 5 P来记录O T R H t e每次状况的改动,可打印日志与调试回溯,也是
  3. reducer: 纯函数,处理 action,核算状况返回最新的状况。

这是 redux 供给的最根本的也是H ? I Y & n s最中心的功用,这儿不多做O j J J解说。再合作 react-redux 将状况 st8 d & hore 与组件相关起来,这样组件就可以订阅到 store 中的状况,完结状况同享。

闪电智能创作平台项目前端总结

前文) B L Z I # K提到组件化之后会出现多a W Z # }层级的 props 传递问题,同级组件之间的状况同F ( Y h M x N享问题,在结合 redu{ | vx 之后i x _ q E,这些问题将会得到处理。9 h e

组件需求去订阅 store 中的状况,在状况产生改动的时分 re-render 组件,这样可以防止逐级传递 props ,一起也很便利的处理了同级组件之间状况同享的问题。

更重要的是,通过合理的订阅 store 中的值,可以大大减小由于状况改动导致的 re-render 的规模,这也是功用进步的一大手法。

此时组件中只N m ` I包含私有的状况和 UI 烘托逻辑,数据现已别离出去,组件也将逐渐变得纯净,这也大大有利于组件的笼统提取。

5. 开发中I f k j ; &遇到的问题

5.1 H5拖拽便利完q C _ l : { B j结元素拖动作用

在三期改版中对视频修改做了许多的调整,为了用户可以8 a ! j x L | 3更便利的运用修改功用,优化了M j I t 2 P许多交互。最大的改动是将视频资源进行拆分,拆分成为文字轨迹、视频轨迹、音频轨迹,c B s |一起可以对不同轨( z , E W Q y迹的内容进行修改,比方添加或替换新的资料、替换转场、添加卖点文字、替换背景音乐等。

在此次优化中产品提出了拖拽元素进行内x Q d v f ] O 2 Q容修改,比方添加新的资料、替换转场、添加卖点文字。

闪电智能创作平台项目前端总结

这是其间一个功用,将资料内容添加到资料轨迹中,然后从头生成新的视频,要完结它却也费了不少功夫。

一开端考虑到用定位来做,可以完结,可是面临的问题将会十分多,核算量也十分大,再加上不同的c 8 L浏览器巨细也会对核算i j { 6形成很大影响,还有组件化开发导致的组件件的通信也会变得愈加杂乱,这种种原因使得我不得不另找出路。

由于项目只需求支持到 IE 10 以上,可以运用 H5 拖拽e | P。那么问题似乎就变得简略了起来。

拖拽的进程很简略,拖动开端 –> 拖动移动 –> 拖动开释。

操作目标 事情 阐明
被拖动元素 dragstart 在元素开端被拖动时分触发
drag 在元素被拖动时重复触发
dY 6 l ` pragend 在拖v 4 V { L动操作完结时触发
目标元素 dragenter 当被拖动元素进入意图地元素空间规模时触发
dragover 当被拖动元素在意图地元素内时触发
dragleavC 2 @ + J He v J y ? N d H s被拖动元素没有放下就脱离意图地元素时触发
drop 当被拖动元素在意图地元素里放+ k / c 4下时触发

可以看到拖拽需求两个元素,一是需求r | O N g一个可以被y : q s x 4 ( _ 9拖拽的元素,二是需求一个承受的目标元素,这/ k } E e B两个元素都有必要添加 draggable="true" 特点y # G }

^ m k个简略的demo:

<div class="container">
&$ ( A c 0 f . Plt;span class=} | W 4 U t o  e"drag" draggal C wble="true" id="drag">l C )拽我</span>
<div class="drop" draggable="true" id="drop"></div>
</div>
let drag = document.getElementById('drag')/ + + l @ * r;
let drop = dU = ^ ^ Aocument.getElementById('drop');
drag.aw $ Y q ) ( 1 9 KddEventListeL 7 1 [ lner('dragstart', (e) => {
cons=  ] J %ole.log(^ m R T  Z - C } ` A z x 3'start');
e.dataTransfer.setData('text', '拽我');
});
drop.addEventListener('drah e qgenter', () => {
console.log('dragenterX / = 3 E 3 1 $');
});
drop.addEventListener4 S * y('drop', (e) => {
console.log('drop');
const text = ex . q ( + K m | :.dataTransfer.getData('text');
drop.innerHTML += text;
});

很简略的 demo 试运行一下,J ] r成果是并没有成功达成预期作用。

查阅资料发现, dragenter 和 dragover 事情的默许行为是拒绝承受任何被拖放的元素。因而,有必要阻止浏览器这种默许行为,否则是不会触发 drop 事情的。

drop.addEventListener('dragove? T @r', (e) => {
console.log('dragover');
e.preventDM # ?efault();
})1 R _;

在需求中对拖动元素的对应数据进行操作,告诉学习才知道要给拖拽目标设置数据的话,需求在被拖动元素开端拖拽的时分以字符串的格式设置数据,比方– S E : 8 , _ e.dataTransfer.setData('text', '拽我'4 q H { 9 N) ,在目标元素开释的时分去接收,consP _ v x D Lt text = e.dataTransfer.g! Q W N 9 T C ^etData('P x v Q 1 } g & _text') ,这点成为完结需求的要害。

drag.addEventListener('dr} 9 D % = Z ( }agst3 C 7 b 8 Cart'o ~ 4 h 1 R q ; 2, (e) => {
console.log('start');
e.dataTransfer.setData('text', '拽我');
});
drop.addEventListener('drop', (e) => {
console.log('drop');
const text = e.dataTransfer.getData('text');
drop.innerHTML += text;
});

好了,看下作用。

闪电智能创作平台项目前端总结

5.2 运B f 0 ` 6 3用拖拽轻松完e – R O N U t *结杂乱的资料添加功用

1 D 8 4 X { % C Z面完结了元素拖拽,拖拽完结s . K } @ k A 0 l之后,还要完结以下功用:

  1. 假如是在之前的视频片段是开释,是替换之前的资料内容(对应下图的绿色区域)

  2. 假如是在资料片段之外的空白区S , & .m 4 8 $ B 1 _ z开释,则是在结尾添加片W . V +段 (对应下图的赤色区域)

  3. 假如是在两个片段之间开释,则是刺进新的片段 (对应下图的蓝色区域)

  4. 两个片段之间还有转场作用,需求扫除这种(对应下图的灰色方块)

  5. 进入可开释区域需求展现蓝色边框

闪电智能创作平台项目前端总结

完结起来是相当杂乱的,需求F e – {对许多的元素进行做监听,一起有必要阻止事情冒泡,才干精确N { +捕捉开释区域。

本次需求中可拖拽的操作包含资料、卖点文字、转场作用,这些不同的功用拖拽开释的区域是有穿插的,或许说在拖拽完毕后该履行哪种操作呢?这就有必要用到 e.dataTransfer.setData(format, data) ,一起e s 3 ) 0 :在拖拽开释的时分依据 e.dataTransfer.getData(format)V $ ! N N a U R =获取数据,只要获取到相应数据才干够进行相应的响应。

还记得上面说的组件化吗,那么这: Y ! 7 Z + M r儿是不是便是个很合适呢?赤色、蓝色、绿色框的部分别离对应一个组件,z M & 9那么在判别拖拽元素% | x | x是否进入该区域并显现边框给出反应,这将变得简略许多,想一想不拆分组件的话怎样区分进入的是哪一个区域呢?

在这期间进行的每一个动作都保存为新的数据,在下次生成视频的时分需求将这些数据传给后台,这儿也相同用到了 redux 进行数据办理,替换资料直接将 redux 中的字段值进行修改,然后从头烘托元素,添加片段也是将 redux 中的 list 进行添加,再烘托列表,这将很大程度上下降了数据办理的难度,并且在; ` 3 3 _ j提交从I E M ? ; Y s 4 ?头生成的时分只需求直接从 redux 中拿取,防止了组件之间的参数传递。

5.8 0 8 , & : 23 奇妙完结组合便利键操作

为了进步用户体会,完结了上一步、下一步删除的便利操作,页面上的每一步操作都需求对数据进行保存,那吊销和删除便利操作其实是对数据的回退和恢复,V + p Q l = k 6 L将数据置为上一个或下一个状况,那该怎样做呢?

本次完结采用了比较粗暴的方法,每一次页面操作都将Z ; i数据进行备份,每一次便利错做则是将备份数据中的某一条设置为当前数据。

// 上一步、删除 
case UNDO_OPERATION:
if (: 2 * {state.stackPointer &O 4 | M 9 4 . Jgt; 0) {
const currentStackPointer = state.stackPointer - 1
const currentVideoDetail = state.operationStack[currentStackPointer]
return {...state, stackPointer: c. 5 ]urrentSta1 m r % % ! - LckPointer}
}
return state
// 下一步
c8 % e F # N q u +ase REDO_OPERATION:
if (stateL Z h R B 0.stackPointer < state.operationStack.length - 1) {
const currentStackP[ r U H fointer = state.stackPointere g _ | } + 1
const currentVideoDetail = stat; ? 7 : p K P Ee.operationStack[currentStackPointer]
return {...state, stackPoiP E 0 - ) r . ) (nter: currentStackPointer}
}
return state

便利键一般都是组合键,比方本次需求的 Ctrl + ZCtrl + Y ,如何才干精确捕获到呢?可H k g以监听键盘事情如:

window.onkeyup = (e) => {
// Ctrl + Z
if (e.code === 'KeyZ' && e.ctrlKey) {
console.log('KeyZ')
return
}
// Ctrl + Y
if (e.code === 'KeyY[ v Z G f [ b' && e.ctrlKey) {
console.log('KeyY')
return
}
}

为什么没有用 keyCode 做判别呢?

在 IOS 和 windows 中 keyCode 的3 : o @ e N N值不尽相同,或许会存在判别不精确的问题,而 e.code 得到的按下键的键名是相同,这就不必考虑操作体系的问题了。

再通过 e.ctrlKey 判别是否按下了 conH , y T ( % P Z Ftrog H { n c O [ F ql 键,这样便能精确识别出组合便利键。

6. 项目优化

通过屡次需求的迭代,再回看代码是不是有一种 “这代码绝对不是我写的” 的感觉。那么在最近几回的迭代中咱们也? i D – o 5 7是在不断的做项目优化,包含打包、页面功用进步等。

6.1 代码k M m P %提取,防止重复打包

以上这些是组件思维在实际项目中的运用,以及为什么需求组件化抽离。其实组件化抽X – C离也为功用优化供给了帮助。

之前项目是将一切的文件打包成一个 js 文件,在最近几回的版别迭代q w = w {I d ? Z K _ U逐渐做了些优化。

打包成一个 js 文件,文件会比较大,会影响加载速度,特别是主页表现的特别显着,或许会有白屏时刻。

闪电智能创作平台项目前端总结

其实终究打包的内容包含第三方模块、公共组件、事务代码,那么可以将第三方和公共组件抽离出来,单独打包。

抽离公共代码都是在optimization.splitChunks 中进行装备。

optimization: {
splitChunks: {
mf D @ ? -inSize: 100,
cacheGroup1 F K 4s: {
vendor: {
prioriT m g yty: 1, //设置优先级,首先抽离第三方模块
name: 'vendor',
t3 2 p ` @ ; C zest: /[\\/]node_mo` - idules[\_ $ 4 z | f y E O/]/,
chunks: 'initial',
minChunks: 1
},
default: {
//公共模块
chunks: 'initial',
name: 'coz ( M M m n { Jmmon',
minChunks: 2, //最少引进了2次
}
}
},
}
闪电智能创作平台项目前端总结

I m M F ~ –共代码抽离出来后,这些代码就下载一次就缓存起来,防止重复下载p B v & n / I j $。这也是组件化开发的一大优点。

可以看到按需加载后b k 2 U $ z Q 第三方模块 vendor 的巨细也小了许多,主页的 main.js8 { z m 9 体积也小许多,其他的 js 和公共模块会在相应的页面再去加载,这样每个& 6页面加载的资= ( 5 8 | [ r 9源体积就会小许多,可以大大进步页面加载速度,一起可以防止网络资源的浪费。

可以凭借 webpack-bundle-analyzer 查看文件体积以及组成部分。

const BundleAnalyzerPlugin = require('webpack-bune . a @ % bdle-analyzer').BundleAnalyzerPlugin;r P g $ } V
const mep A Z a d { Q Jrge = require('webpack-mK R w a $ f 1 } erge');
const baseWebpackConfig = requi- X , } Jre('./webpack.config.base');
module.exports = merge(baseWebpackConfig, {
//....
plugins: [
//...
new BundleAnalyzer; 3 * @ ! m 1Plugin(),
]
})
闪电智能创作平台项目前端总结

凭借这个插件可以对文件巨细进行剖析,依* 0 T b 2 G 6据实际需求进行进一步的拆分,添加 cacheGroups 的设置即可。

6.2 按需加载,进步页面加载速度

之前项目是一次性加载一切 js 、css 资源,最近开发中改动成按需加载,用来进步主页加载速度,节省网络资源。

结合按需g ~ . y g #加载,可以将文件拆分的更小,模块提取的粒度更细些。在闪电项目中用到页C f j = 2 3 o _ H面按需加载,打包成果如下:

闪电智能创作平台项目前端总结

新版 React 供给了按需加载的处理计划,那就用起来。

import React, { lazy } from 'reactY } E';
const Index = lazy(() => import('./views/I + { _Index'));
...

要烘托哪个组件再去加载对应的 js ,单个 js 的体积也是很小的,将会很大程度上进Q M 0步页面加载速度。

代码拆分打包后会有多个 chunk ,加载过一次就会被浏览器缓存,那么按需加载的时分被加载的资源就会变得越来越少。

6.3 页面架构优化,下降保护本钱

项目之初是将公共的头尾组件在每个页面中引进,也便是说每次路由跳转都要从头加载和烘托头尾组件,而头尾组件中有一些用户信息的接口,这会形成每次切换路由的时分都会从头恳求,这在大多数状况下是没y / ] _ P必要的,为了削减不必要的烘托和接口恳求,并次页面架构优化^ / V H u ) M { c。将公共头尾提取出来Y P 5

ReactDOM.re` Z R R W 6 !nder(w | V Y 5 d 8 G z(
<Provider store={store}>
<Suspense fallback={M o 6 # }<div className="loadingc w X @ _ 5 l _ 5">loading</div>}>
<HashRouter>
<div className="container-wrapper">
<Route component={Header} />
<Switch&gs 0 Kt;
<Route path={routePathI [ ; q ^ Ws.INDEX} exact compo9 b q 3nent={Index} />
<Route path={routePath4 O 1 v ` L D ps.SCENE} component={Scene} />
<Route path={routePaths.FAVORITES} istore={store} component={Favorites} />
...
<Redirect to={route6 z }Paths.INDEX} />
</Switch>
<Rous { #te render={props => {
l| J l 7 # N uet hasFooterRoute = [routePaths.INDEX, routePaths.JOINVIP, routePathv b g `s.LABORATO- @ ] a + 5RY];
return hasFooterRoute.ind 6 0 UexOf(props.location.pathname7 O F u u ) > -1 ? <Footer /> : null
}} />
</div>
</HashRouter>
<4 8 C/Suspense>
</Provider>
), document.getElemep ? X t g 3 UntById('roo9 } 6 O r &t'));

路由改动仅仅加载 Switch 中的6 7 } s n %内容组件,头尾将固定。

闪电智能创作平台项目前端总结

头部 Header7 k ! K 8 c ] y 组件中! i * ^ 6 y & _ b页包含比较多的逻辑,比方头部! m K a #中的导航在不同的路由下展现的内容是不同的,之前由于 Header 涣散在各个页面组件中,对应的逻辑也涣散在各个组件中,这样其实是很难保护的,2 S 2 ] 7 3 M那么本次o j a k i | }也将依据理由! , R ; ] G的改动在 Header 中对这些逻辑会集处理,这更有利于组件的保护。

这样做有c x O 0 U ^ h一个问题,之前在另一个项目中也遇到过,当 Header 作为一向展现的通用组件,如安在每次路由切换的时分更新它所依赖的数据呢?

我最开端运用的是最暴力的方法,运用 Route 的 render 特点,在路由切换时先更新数据再烘托组件。

 <Route
render={props => {
getUserInfo(); //5 I c . .a f ) K A o 7 ?新数据的方法
return <Header /&+ _ K # C h  9 fgt;
}}
/>

这个方法看似处理了需求的问题,可是有个十分显着的坏处,便是路由每次改动都要从头烘托组件,其实有许多的的烘托是彻底没必要的,可是这种状况下并没有方法进行阻拦,并且即使是相同的路由,组件也会从头 render 导致进行了没必要的接口恳求和组件烘托,一番折腾尝试总结了以下几种更! ` 5 3 b = * c好的路由监听计划。

6.3.1 history 监听

class Header extends Component {
componentDidMount() {
// 监听路由的改动,假如路由产生改动则进M F & y + 4K l , } S 8 { $ p相应操作
thH L , [ @ 9 U z Kis.props.history.listen(location => {
// 最新路由的 location 目标,可以通过比较 pathname 是否相同来判别路由的改动m  5状况
if (this.props.locaty r y % H , S , Eion.pathname !== location.patE { J ^hname) {
// 路由产生了0 2 m d 4 I改动
}
})
}
}

6.3.2 组件从头烘托前后

class Header extends Component {
componentWillReceiveProps (nextProps, nextState) {
if (this.s { d 2 Iprops.location.pathname !== nextProps.location.pathname){
// 路由A ^ ( J R产生了改动
}
}
}

相同 componentDidUpd2 v & ) [ ? ; = ZatecomponentWillUpdate 两个生命周期中也是可以去识L x E 7别出路由改动的。

以上两种方法再结合 shouT p : 6 7 Q L UldComponentUpdate 可以p # a x |削减组件不必要的烘托

class Header exp 0 , v B 7 u j jtends Component {
// 当路由产生改动的时分再从头render
shouldComponentUpdate(nextPropsT L } ? % J) {
let prevRP ~ q , 3 d 5 ]outeName = this.props.location.pathnamT | , ? ^ z . e;
lz Y ]et currentRouteName = nextProps.locH , X O W ; : 2 hation.pathname;
return prevRouteName !== currentRouteName;
}
}

6.3.3 hooks 方法监听

import React, { useEffect } from 'React';
const Heade% G E * mr = function (props) {
useEffect(() => {
consolT %  9 @ Y O 2e.log(props.location);
}, [props.location])
}
export default Header;

其实监听路由最重要的便是监听 props 中 location 目} + # /标是否产生了改动,改动了则阐明路由改$ j H * G 6 7 d动了,反之则不变。

监听的机遇,一种是组件挂载后运用 this.props.history.listen ,其他一种是监听 props 的改动,这包含了 componentWi5 s j 7 $ 7 TllReceivePropscomponentDidUpdatecomponentWillUpdate,也便是组件接收新的 props,组件即将更新,组件更新完结这几个时刻节点,hooks也是相同监听的是Y ~ z props.location 的改动。

由所以旧项目没有运用 hooks,终究我运用了 this.props.history.listen + shouldComponentUpdate 的方法,在新项目中运用 hooks 将会变得愈加的简略便利。

总结

通过将近两年的时刻不断打磨的闪电智能内容创造渠道现已逐渐安稳,功用越来越强壮,运用的商家越来越多,产出内容掩盖到的产品品类也多了起来,正在逐渐产品化商业化。关于咱们前端来说进步和改进的空间还很大,榜首次在将 AI 和前端进行结q z K c 4合,咱们正在探寻更成熟更高效的技能计划,这对咱们来说是时机更是应战。