对React运用的一些思索

媒介

  由于笔者对React的相识不深,即使算是进修React的时刻,到如今也才方才半年,所以毛病不足之处还望斧正。以下都是基于React 15(可以有些是16),webpack1举行议论(注:未进修过Vue,Ng,Ember,Cycle,Immutable,Redux-Saga,Mobx,Observable,Rxjs等等,所以可以有些方面已被提及或许处置惩罚了,希望不要介怀)。

正文

本文的排版可以不那末正派,想到哪写到哪,包涵包涵。

组件

  • JSX

    • 备受关注(吐槽)的if题目: 要议论这个题目,首先要邃晓jsx是转换的是什么,只要如许,你才邃晓为何不能在jsx内里写if语句,switch语句,为何只能写表达式等等。好了,回到正题,我们困挠,那末其他的顺序猿肯定也是如许滴,那末正确的姿态是什么呢?没错,提isssue&议论,我们先来看看已有的提出的一些计划(来自JSX的issue,其他的诸如三目,把逻辑提取出来写在表面,多return体式格局,IIFE,do expression等等这里就不多说了):
    • NO.1

      https://github.com/facebook/jsx/issues/65#issuecomment-254056396
      
      <if {myCondition}>
        <div>This part only gets shown if myCondition is true</div>
      </if>
    • NO.2

      https://github.com/facebook/jsx/issues/65#issuecomment-255465492(官方职员提出的,所以采纳的概率轻微要高一点,基于do expression)
      
      <div>
        Hi!
        {if (bool) <MyComponent /> else <div></div>}
      </div
    • NO.3

      https://github.com/facebook/jsx/issues/65#issuecomment-255484351
      
      <If
        condition={ condition }
        then={ <component /> }
        else={ <component /> }
      />
      
      function If(props) {
        const condition = props.condition || false;
        const positive = props['then'] || null;
        const negative = props['else'] || null;
        return condition ? positive : negative;
      }
    • 固然,新的语法就意味着新的标记(平常来说),所以这类变化不肯定是好的,也不肯定每一个人都能接收。
    • 比方bjrmatos说的

    “It’s just JavaScript, not a template language” -> no need to replicate JS functionalities with custom syntax. That is the main benefit of JSX IMO, seriously is so easy to do this with js even if it looks “weird” (for me it is not weird, it is just the syntax of the language)

    • 比方jmar777说的:

        [if (condition)]
          <Foo />
          <Bar />
          <Baz />
        [/if]

      We instead need:

      { condition && <Foo /> }
      { condition && <Bar /> }
      { condition && <Baz /> }

      If the condition itself gets more complex, then the code gets uglier, or alternatively we need to store the result to a temporary variable, which further increases the verbosity. I will note that this particular pain point would be eased by better support for fragments.

    TBH I’ve never seen a specific proposal that I’m overly fond of, but inasmuch as branching logic is integral to all but the most trivial of rendered outputs, I think there’s a compelling case to be made for first-class support in JSX.

    • Atrribute autocomplete(这也是我们的希望之一,毕竟可以削减许多无用功,代码也更整齐)
render () {
  const {
    prop1,
    prop2,
    prop3
  } = this.props

  return (
    <MyComponent prop1 prop2 prop3 />
  )
}

Or

render () {
  const {
    prop1,
    prop2,
    prop3
  } = this.props

  return (
    <MyComponent {prop1} {prop2} {prop3} />
  )
}

Instead Of

render () {
  const {
    prop1,
    prop2,
    prop3
  } = this.props

  return (
    <MyComponent prop1={prop1} prop2={prop2} prop3={prop3} />
  )
}
  • 希望支撑数字attribute省略花括号

  • 另有的人希望关于字符串范例的attribute,可以省略引号。seb末了也诠释了为何没有许可可以对字符串属性举行简写(由于存在两种差别的语义,假如许可去掉,究竟以哪一个为准呢)

https://github.com/facebook/jsx/issues/25#issuecomment-137224657

I think the conclusion is that since these forms are currently semantically different in JSX parsers:

<input value="Foo &amp; Bar" /> // Foo & Bar
<input value={"Foo &amp; Bar"} /> // Foo &amp; Bar

We should keep it consistent which would mean that this would also be different:

<input value=`Foo &amp; Bar` /> // Foo & Bar
<input value={`Foo &amp; Bar`} /> // Foo &amp; Bar
  • Attribute Destructuring
https://github.com/facebook/jsx/issues/76

<Foo {fizz, buzz}={bar()} />

// 固然也可以先解构了再赋过去,不过那样肯定是要贫苦一点。末了seb也说了,假如https://github.com/RReverser/es-borrowed-props这个提案有希望的话就更好了
  • Attribute Or Children

    • 写过组件的同砚应当偶然刻会碰到如许的状况,我们须要挪用方传过来一个组件,那末题目来了,这个组件究竟是以children的情势照样props的情势传过来呢?偶然刻我们只须要一个组件,那末都可以(固然另有斟酌语义和人人的惯性头脑)。假如须要多个组件就不行了,就须要举行挑选,偶然刻明显以为都该以children的情势传进来,然则没法必需做出弃取。所以我们可以看到比方有些同砚就提出了:
    • NO.1 attribute2Children形式
https://github.com/facebook/jsx/issues/65#issuecomment-254060186

<SplitContainer
   left={<div>...</div>}
   right={<div>...</div>}
/>
// vs
<SplitContainer>
   <.left>
     <div>...</div>
   </.left>
   <.right>
     <div>...</div>
   </.right>
</SplitContainer>
  • NO.2 style(实在也是attribute2Children形式)

https://github.com/facebook/jsx/issues/65#issuecomment-254100455

<style>{`
    .your-css-here {color: #333;}
`}</style>
// or
<style>{component_style}</style>
  • 关于bind的题目,如今都基本用class里用箭头函数的体式格局了。然则照旧存在一个题目,在某些状况下我们照旧须要运用bind(固然用闭包也可以,不过面对的题目是一样的)。比方,我们想给我们的处置惩罚函数通报参数,我们就不得不运用诸如<Foo bar={this.handleSomething.bind(null, arg)} />的体式格局,然则尽人皆知,如许会构成Foo组件的props每次都邑变化,关于PureCompoennt可以有机能影响,(虽然假如这里不是bar而是ref的话不会存在如许的题目,由于浅比较的时刻基本不会斟酌keyref),固然我们可以本身定义shouldComponentUpdate然后本身手动比较下,假如nextPropsarg和如今的arg一样的话(固然这里会依据arg是不是是对象比较的战略会不一样),那末我们就采纳之前的函数(假定我们缓存了)。
  • 如许一看好像还行,然则依然存在一个题目(实在我们完整没有必要斟酌这些,由于对机能真的没啥影响,机能基本不会经由历程这些获得改良或许下降,然则本着深挖洞,广积粮的精力,思索思索照样可以的)。什么题目呢,就是假如这个arg是一个函数或许说是包含函数的对象,那末两个函数相称就可以推导出他们真的相称吗?或许说两个函数不相称就可以推导出他们真的不相称吗?明显不能如许,由于函数可以“不纯”。这就给我们的推断事变带来了肯定的影响,之前没有失足过是由于失足的概率本身就很低,由于props和state本身都是“纯的”,没有人会去手动修正它(即直接赋值)。我们不那末仔细的来看一下下面的例子:

     class Bar extends Component {
    shouldComponentUpdate (nextProps, nextState) {
      if (this.props !== nextProps) {
        // 题目就出在这里,假定今后我们须要挪用p2
    
        if (nextProps.p2 !== this.props.p2) {
          // 那末这里究竟该返回true照样false呢
          // 返回true,那假如p2()的值没变怎么办,算不算是一种糟蹋
          // 返回false,那假如p2()的值变了怎么办
        } else {
          // 这里p2相称了,我们能在今后挪用之前缓存的p2吗(假定缓存过了,固然缓存的前提平常都是pure的才缓存哈)?也不能,由于p2可以从一个“纯”的变成“不纯”的了。别的返回false照样true和上面的同理。
          }
         
       // 所以末了我们会发明,我们基本没法推断,由于我们不晓得p2究竟“纯”照样“不纯”
      }
    }
         
       render () { console.log(...this.props); return null; }
     }
      
    let externalData1 = 'extenalData1';
    setTimeout(() => externalData1 = 'externalData2', 1000);
    let util = (...args) => utilFunc(...args, externalData1);
      
    class Foo extends Component {
    state = { arg1: '1', arg2: util('aa') }
      
    componentDidMount () {
      this.setState({ arg1: '2', arg2: util('bb') });
    }
     
    handleSomething (...args) {
      console.log(...args);
        
      ...somethingElse
    }
      
    render () {
      const { arg1, arg2 } = this.state;
     
      return (
        <Bar p1="p1" p2={this.handleSomthing.bind(null, arg1, arg2)} />
      );
    }
    }
  • 组件

    • 平常来说,我们将组件分为两大类,即基本组件与营业组件。在React中,基本组件基本已由Ant Design(PC端)掩盖终了,未来可以会有一些更改/更新,然则都不会太大。基于基本组件的封装/改写,照旧属于基本组件。营业组件基于基本组件,此时就面对一个非常重要的题目,数据/数据流相干的处置惩罚(这个背面再谈)。除此之外,主要想提及一下,还应当有一类组件,临时称之为逻辑组件,为何要分出来呢,由于确切觉得这两类都不太能正确的形貌它。比方,<If><Visible mode="null | opacity | visibility"><Auth>(或许<OnlyShowWhenIsAdmin>之类的)我们的体系基本就是由这三种组件举行拼装。
    • 表单。

      • 我们从最简朴的提及。一个input,不包含任何联动。那末如今存在一个题目,就是每一个input我们都要竖立与之对应的onChange函数(假定我们每一个input都采纳Controlled Components的情势,由于可控性要好一点。同时都有本身的逻辑,不单单是setState(e.target.value)),太贫苦了。抱负的状况是怎样的呢,我们应当可以采用动态竖立的体式格局,只须要供应字段名,自动将值注入到state中,以及自动在组件的this或许原型链上建立对应的onChange函数,然后再赋给input(固然如许的话基本上要完整依靠供应的组件库,不能希冀本身随意写一个input也能自动到达如许的结果)。
      • 那末题目来了,这里须要加糖(使代码更少)和defineProperty(或许Proxy)(使修正更直接轻易)吗,个人以为,都不可。由于这都邑增添debug的难度。我照样更倾向于前面提到的简朴封装,然后照样用setState去转变字段。如今盛行的主要有antd的form以及redux-form。然则个人以为这并非最好的体式格局,由于觉得有点乱,举个比方的话,觉得有点像写传统的模板一样,固然,或许就是要特地治治我们这些处女座。
      • 下面说说联动的状况。在之前(如jQuery),我们要处置惩罚联动,必需手动保护联动的关联,所以要么在onChange内里依据逻辑手动触发其他组件的更新,要么是采纳after或许callback queue的体式格局,总之,重心是保护逻辑而不是数据。那末是不是应当存在一种体式格局,让我们的重心靠到数据上面?如许debug,开辟,保护都轻松一些。关于React运用而言,自然的处置惩罚了部份题目。但还存在一些题目,比方,单向数据流致使的偶然数据链过长过烦琐(所以才产生了redux),须要在多地保留一致份数据等等。
      • 以上仅仅是一些思索,关于表单的探究还没有最先(由于如今表单相干的需求还不是许多和庞杂,过段时刻研讨研讨今后希望能处置惩罚一些题目)。
  • Encapsulated Compose Or Custom Compose

    • 举例来说。比方响应式,我相识到这个观点应当是从bootstrap的栅格最先,到背面antd也有对应的Grid体系,包含了Row和Col。那末题目来了,当完成一个响应式列表或许table的时刻,是不是应当本身去组合,即哪些处所写个Row,然后下面的Col的sm,md等等顺次是多少,挨着填进去。照样说我们先把这些组合逻辑封装好,比方:

      const responsive = responsivelizeComponent({
        xs: 1,
        sm: 2,
        md: 2,
        lg: 2,
        pc: 3
      });

      然后挪用,传入数据,以及Row,Col对应的组件:

      { 
        responsive(
          list, 
          () => <C ... />, // 这里假如C本身就是Row,那末就举行props兼并,假如不是Row,那末须要在处置惩罚的时刻包裹在Row内里
          () => <Item ... />
        )  // Item同理,只是换成了Col
      }
    • Component Middleware: 我们晓得,中间件的观点由来已久了(汗青就没有去考据了,node也不熟习就不谈了哈哈)。在redux中,我们就可以建立一系列的中间件行止置惩罚我们的action。那末,组件是不是也可以具有如许的才能呢(即组件中间件)?答案是肯定的,decorator就是一个很好的例子,然则decarator与middleware照样存在肯定的差异,一是誊写上不直观/雅观(如今个人能忍耐的就是最多1个@),二是掌握的力度太小。三是没有构成一个团体,各个部份只能跟本身的上游打交道。这些方面才方才最先探究,就不在大佬们眼前介(zhuang)绍(bi)了。
  • 数据流

    • Redux

      • 细粒度的actionType:

        • 如今来说,我们的action的type以至是action照样设想得太甚简朴。我们完整可以进一步举行设想来削减我们的事变量。比方,我们可以看到redux初始化store时的actionType为@@INIT,受此启示,我们完整可以自定义更庞杂的actionType以至action,设置中间件举行处置惩罚。比方@@FOO@@BAR/xx_TODO,或许像官方例子中的那样const CALL_API = symbol('CALL_API'); action = { [CALL_API]: {...} },然后竖立与之对应的middleware,从而削减reducer的数目。固然这是一个开首,庞杂运用应当是存在许多个middleware举行职能分工(没有庞杂运用的履历,这里就不多说了)。
        • actionType可以与响应的处置惩罚的函数的名字构成包含关联,削减switch里的case代码量。比方,在reducers/foo.js中,声清楚明了一系列的handlexxxxType,那末我们可以在foo.jsimport * as all from './foo.js';,然后建立一个相似于mapActionTypeToHanleFunction的处置惩罚函数。这里就不用在每一个case都去挪用某个函数了。
        • 一次用户操纵致使的多个位置的数据更改是不是都可以经由历程接连触发差别的dispatch action来完成?答案是不是定的,比方dispatch某个action,目标是删除某个实体,然则同时sotre中其他某个处所存储了这个id,或许说一个ids数组里包含了这个id,那末在此次dispatch完成今后的更新里就会失足,所以我们不能再dispatch一个action去同步的删除这些数据。我们只能在差别的reducer内里监测第一个dispatch的action,然后处置惩罚。
        • 另有一个就是群里产业聚大大提到的,action实际上和HTTP要求非常相似,可否用处置惩罚HTTP要求的体式格局去计划redux?
      • Redux-Orm:望文生义,这就是一个ORM库(毕竟这是一个前端的数据库嘛,之前想过redux合营indexDB,背面发明也不是很轻易)。运用后的感觉是,确切比纯的redux轻易一些(空话,不然人家建立这个干吗)。详细的比较等今后用了mobx,immutable和rxjs今后再放在一起比较吧。
      • React要想触发更新只能采纳setState(抛开forceUpdate不谈),所以这就限定了我们修正数据 -> 自动触发一切相干更新这类操纵,其他的运用我们可以纪录所以依靠于这个数据的组件,然后修正数据的时刻顺次触发它们。但React的话要想完成这个就必需绕一个圈子,如redux计划。同时还存在一些弊病,比方:

        • 一是依靠某个数据的明显只要A,B组件,为何我还要挨个关照C,D组件,C,D组件明显是依靠于别的的数据的。即使react-redux内部举行了许多机能优化来防止不必要的更新(本质也就是只管确保两点,一是我不须要的数据不应当触发我的更新,二是即使是我须要的数据,没变化的状况下也不应当更新)。由于redux和react-redux是断绝的,redux就是一个数据(包裹/快递)堆栈,当有包裹来的时刻,他可以在本身内部对包裹举行分类,把包裹放在指定的某个或某些地区(固然数据自然是可copy许多份的,包裹只要一份,就不要纠结这个啦),然后呢,包裹要出库该走堆栈的哪一个门呢?不清楚。。type只是堆栈内部份类时用于参考的一个东西,对外部并没有作用。
        • 所以它只能顺次翻开堆栈的一切门,拿上扩音器大呼,堆栈包裹更新啦,快来看看你们那儿要不要搞些事变~,这里实在就存在一个题目,堆栈里哪块地区的包裹更新了实在堆栈本身是可以晓得的,然则如今他没有做纪录,这就致使了收到关照今后的前来的公司又必需亲身去堆栈里找本身须要的包裹(selector),与此同时,堆栈也不能保证这批包裹属于这一次包裹更新中的包裹(即selector出来的并没有发生变化)。所以这就构成了资本的糟蹋,有可以一次dispatch末了堆栈里没有任何包裹更新,堆栈也必需挨个翻开出库门。二就是前面提到的有更新也不应当挨个翻开出库门。
        • 因而,可以地更好的一种体式格局是。组件应当把本身可以须要的包裹提早通知堆栈,细粒度一点的,固然既可所以就在type和包裹间竖立一种映照关联,如{type: { name: 'update_todo', from: this, need: ['foo', 'bar'] }}foobar是store中的某个key), 粗粒度的话(有些case可以不能如许做),就不本身去添need参数了(然则照样要写from),可以直接让redux本身举行统计,由于一个action致使了哪些reducer发生了变化这个它是可以统计的(也不难,很早的版本中在combineReducer中就有hasChanged的flag了),末了也能构成一个typeneed的映照。然后组件也把need参数传给connect,同时,redux内部的listeners就不应再是一个简朴的回调数组了,须要按need举行归类,如许我们才保证不是去关照每一个subscribe了的组件,而是确切须要此次更新的我们才关照。另有就是,既然有了need,也不再须要selector了,我们在关照的时刻就自动在store中把对应的need传过去了。(这里须要注意到是,关于实体数据,store存储的数据肯定照样要normalize后的,不然数据冗余很严重,然则我们关照的时刻没有必要再denomalize解范式化或许说像传统的挨个把外键转换为实体,我们直接竖立一个reducer存储当前的dispatch对应的收集要求的response)
        • 别的这些方面徐飞叔叔的文章真的是写得非常非常不错,本身的主意许多时刻就是小巫见大巫了,今后肯定照样要抽闲多揣摩几遍。

    CSS Modules

    • 全局与部分: 从全部项目来说,可以将第三方非CSS Modules模块的css一致放在一个目次下,不做CSS Modules处置惩罚。部分的话用global语法就好了。
    • 模块复用: 比方有两个css模块,a.cssb.css,我们晓得,composes是给对应的标签的class加上某个css类,而不是像传统的使两个类都具有一样的划定规矩。关于在营业组件中composed基本组件如许还好,然则假如是composes其他的营业组件,就会显得有点怪怪的,比方<div class="a-foo b-bar">ssss</div>,明显是一个和a组件相干的div,却不得不打上b的烙印。同时,我们还不能只用b-bar,哪怕我们的a-foo没有东西,也必需写成.a { composes: b-bar from 'b.css' }才构成复用。而我们又不太可以零丁把b-bar提取出来作为一个大众组件。
    • 定名: 模块化能肯定程度上削减长定名,然则没法完整消弭长定名。由于缺乏须要它。比方一个组件内的一个list,照旧须要写成listlist-item,比拟之前,省去了xx-list中的xx。但有些状况下,比方item下面的内容很少,就一两个,我们不想零丁提取出来,那末就得写成list-item-xxx,确切看着有一点不雅观。
    • Css的模块是不是应和js模块耦合? 一般来说,我们一个css模块会对应一个js模块(撤除皮肤,改版这些)。然则除此之外,是不是应当存在零丁的CSS模块,它们不属于详细的某个基本组件或许营业组件,而是同享给这些组件去运用或许说组合。如今这方面没有实践过,就不多说了。
    • Css Modules与React连系

      • 如今已有的计划有react-css-modules以及babel-plugin-react-css-modules,它们的作者是一致个人,后者是前者的革新版本,这位作者也是medium上那篇人人熟习的stoping css in js的作者。
      • 前者(react-css-modules)存在的题目有,一是机能,由于采纳的是HOC的形式,致使在临盆环境每次衬着也要走一遍检索css映照表的历程,构成机能消耗。二是誊写体式格局贫苦,即运用了decorator,也照样要总是反复一样的代码。三是某些状况下要在一个组件内运用屡次HOC,整齐性和轻易性都不太好。这些在README中基本都有提到。另有就是不能写空的css划定规矩,内部对此会抛出非常(之前被这个坑过,恰好谁人时刻的chrome版本吞错,找了良久才发明是这里的题目)。背面跟作者交流了一下,提出了空的css划定规矩的实用场景和初志,所以在babel-plugin-css-modules中关于此采纳正告而不再是抛出非常。
      • 后者(babel-plugin-react-css-modules)基本上处置惩罚了上述提到的一切题目。然则另有一个处所值得思索一下,那就是import机制的题目,个人以为还须要增添一种特征(可设置关于每一个js模块是不是启用),即import多个css也不须要指定importName,默许背面的会掩盖前面引入的css中的同名class,不然我们照样要写成foo.a bar.b的情势,丧失了我们运用这个的部份初志。
      • 别的采纳如许体式格局临时有个瑕玷就是IDE支撑不好(没有自动补全提醒),只管7月份宣布的最新版webstorm对原生的CSS Modules支撑度更好了(style.这类体式格局后会有自动补全的提醒)。

    其他

    • npm是不是应当具有更强的束缚?即语义版本号必需满足语义所给予的前提,从而让打包的时刻不会1.0.1和1.0.2都打包,应当只打包1.0.2,谁人依靠1.0.1的包转而去依靠1.0.2,从而使得只打包一个。更有主意的是,上传到npm上的包必需供应相似react-codemod的机制(这个确切很难,不知深度进修可否有协助),从而可以让一切运用这个包的运用无痛自动晋级,从而完成哪怕是有breaking change的版本更新,比方1.0.2变成1.1.0,末了也只会打包1.1.0。之前包含如今,这部份内容都是经由历程changelog或许官网更新的体式格局发放出来。然后让开辟者本身行止置惩罚。
    • 一个目次下有多个js文件,此时平常会竖立一个index目次去导出这个目次下一切js里的default以及一般export的变量(固然前提是没有反复的),目标是为了import的时刻直接import index文件,防止去影象某个变量在哪一个js文件中。然则如许有一个贫苦,那就是每次新增js文件的时刻都须要记得去index举行export。那末我们为何要背道而驰的去搞一个index文件呢,我们的目标不就是要import轻易吗,为何不让编译器(实在webpack的alias也可以,然则毕竟是第三方,包含fb内部也有本身的模块导入体系,然则弊病他们也说了)去自动寻觅包含我们要引入的谁人变量都有哪些js文件然后给我们一个清单呢,就像JAVA的import机制那样,按ctrl+shift+o自动import,若有反复的会让你挑选究竟以哪一个文件导出的为准(固然也有些特殊状况,比方我们须要用as取别号)。
    • 越庞杂的项目,粒度越小,耦合度越低的项目,组件的数目会成倍的增添,如fb就有3W个组件。那末,我们怎样推断某个组件我们或许他人或许团队之前是不是是写过,假如是,我们又怎样晓得这个组件的名字是什么,在哪一个位置?(fb虽然有fbjs是开源的,然则谁人有点像util,而不是component库,所以也不晓得内部究竟怎么搞的,之前也只是问过关于react源码他们是不是是有比较轻易的管理工具,可以疾速定位某个功用或许名字或许解释在哪一个处所,获得的答案是,没有)。

    毛病处置惩罚(16beta昨天刚宣布了,官方有文档了,就看官方的啦),构建,测试,体积优化,布置今后再谈了(实在是由于没什么履历)。嗯,此次就先如许吧

        原文作者:ne_smalltown
        原文地址: https://segmentfault.com/a/1190000010401829
        本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
    点赞