<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          React 表單源碼閱讀筆記

          共 36980字,需瀏覽 74分鐘

           ·

          2021-02-28 21:22

          1 概念

          1.1 什么是表單

          實際上廣義上的表單并不是特別好界定,維基上講表單是一系列帶有空格的文檔,用于輸寫或選擇。更具體的,在網(wǎng)頁中表單主要負責(zé)數(shù)據(jù)采集的功能,我們下文中所提到的表單都指后者。如下圖展示的是 Google 個人資料中配置頁面更改姓名的表單:

          1.2 表單的職責(zé)

          表單通過適當?shù)?UI & 交互,將用戶的輸入轉(zhuǎn)化為特定的數(shù)據(jù)結(jié)構(gòu),js 中通常是對象然后傳輸過程中通常是 json,例如上述示例中很可能實際的表單數(shù)據(jù)。

          {
            lastName: '韓'
            firstName: '紅'
          }

          更具體來說, 表單主要需要解決以下一些常見的問題:

          1. 具體的表單項對應(yīng)的 UI 組件, 例如輸入框, 下拉框等等;
          2. 圍繞 UI 組件,其對應(yīng) label 的樣式以及錯誤信息的整體布局組織;
          3. 表單校驗, 實際包括簡單的校驗以及依賴型校驗;
          4. 嵌套數(shù)據(jù)的表示, 例如列表&對象;
          5. 字段與字段之間的聯(lián)動。

          2 React 官方表單方案

          React 官方給出的表單方案非常簡單, 直接看官方文檔就可以 https://reactjs.org/docs/forms.html。總的來說,官方給出了兩種不同的表單方案,基于受控組件以及基于非受控組件的表單實現(xiàn),當然前者會比較常見一些。所有的第三方表單都可以認為是這兩種方案的延伸及封裝。

          2.1 受控組件

          簡單來說就是指由父組件完全控制該組件的狀態(tài)及回調(diào)函數(shù),子組件的狀態(tài)變更需要通過回到函數(shù)通知到父組件,由父組件完成狀態(tài)變更后再將新值傳回子組件。

          表現(xiàn)在代碼上就是形如之類的表單組件同時接收 value 以及 onChange 這兩個 props 來實現(xiàn)受控。下圖給出一個官方的實例:

          class NameForm extends React.Component {
            constructor(props) {
              super(props);
              this.state = {value''};

              this.handleChange = this.handleChange.bind(this);
              this.handleSubmit = this.handleSubmit.bind(this);
            }

            handleChange(event) {
              this.setState({value: event.target.value});
            }

            handleSubmit(event) {
              alert('A name was submitted: ' + this.state.value);
              event.preventDefault();
            }

            render() {
              return (
                <form onSubmit={this.handleSubmit}>
                  <label>
                    Name:
                    <input type="text" value={this.state.value} onChange={this.handleChange} />
                  </label>
                  <input type="submit" value="Submit" />
                </form>

              );
            }
          }

          2.2 非受控組件

          剛說到受控組件所有的狀態(tài)都由外界接管,非受控組件則恰恰相反,它將狀態(tài)存儲在自身內(nèi)部,我們可以借用 React 中的 ref 來訪問它。同樣還是官方的例子:

          class NameForm extends React.Component {
            constructor(props) {
              super(props);
              this.handleSubmit = this.handleSubmit.bind(this);
              this.input = React.createRef();
            }

            handleSubmit(event) {
              alert('A name was submitted: ' + this.input.current.value);
              event.preventDefault();
            }

            render() {
              return (
                <form onSubmit={this.handleSubmit}>
                  <label>
                    Name:
                    <input type="text" ref={this.input} />
                  </label>
                  <input type="submit" value="Submit" />
                </form>

              );
            }
          }

          2.3 該用哪個

          關(guān)于受控 vs 非受控的選擇,我查閱了許多資料,大部分文檔認為應(yīng)該優(yōu)先考慮受控模式,可惜還是沒有找到一個能讓人信服的理由。大量文檔無非是列舉受控之于非受控更靈活,更 React 單項數(shù)據(jù)流,感覺更像是為了政治正確而不是基于真實的開發(fā)體驗來看這個東西。甚至有一張廣為流傳的對比圖:


          我個人認為這個圖實際上是有問題的,下面所列舉的一些非受控組件無法覆蓋的場景,實際上 ref 配合組件 onChange 是可以做到的,例如字段級別實時校驗,我們完全可以在字段上掛上監(jiān)聽函數(shù),在其值發(fā)生改變的時候進行字段校驗,然后通過 ref 控制組件的內(nèi)部狀態(tài)。所以我認為上述場景并不能作為不推崇非受控組件的理由。

          我們之前也討論過這個問題,有一個比較有意思的說法是:

          實際上問題的關(guān)鍵不在于 react,而在于 react 實現(xiàn)的背后思想是 ViewModel。理所應(yīng)當?shù)模瑀eact core team 希望使用 react 開發(fā)出來的東西也應(yīng)該受 viewModel 控制。


          但是我個人的理解是, 受控和非受控是站在組件狀態(tài)(值)的存儲位置來看的,或者說是基于組件是否是所謂的 Dummy Compnent,本質(zhì)上受控與非受控的表達能力是相同的,從某種層面上看可以互相實現(xiàn)。

          3 React 社區(qū)表單方案

          官方給出的方案雖然簡潔直觀,但是直接拿來寫需求的話還是有些簡陋的,場景稍微一復(fù)雜其實效率不是很高。所以很自然地,React 社區(qū)給出了相當多的三方表單方案,下面我會分別提幾個比較典型的來講。要注意的是,由于許多設(shè)計上各個表單都是互通的(互相借鑒)的,所以有些功能(例如對嵌套數(shù)據(jù)的支持)的規(guī)范/實現(xiàn)我只會挑一個表單來講。

          3.0 前置概念

          在深入各個表單方案之前,我想補充一個前置的概念,即 Field。那 Field 是什么?

          Field 的概念比較寬泛,它可以簡單理解為表單組件(比如輸入框)的抽象體。要更具體的討論這個問題,我們可以先從頭來看,我們要如何在 React 當中寫一個表單?

          1.  從受控的角度來講,首先我們會定義一個 state 來管理表單的狀態(tài),然后我         們會給每個表單組件掛載上 value+onChange。

          1. 接下來我們可能會希望加上表單項的校驗規(guī)則的定義,添加標題,聲明 name,完成表單項與底層數(shù)據(jù)結(jié)構(gòu)的映射,其實就是 Field 的作用。所以 Field 主要是幫我們解決了具體表單組件的狀態(tài)綁定、校驗綁定,以及其他的一些像 label 的設(shè)置甚至樣式,錯誤信息展示等一系列通用的邏輯。

          2. 我們熟悉的 antdesign 表單中的 Form.Item 其實就可以理解為一個 Field。


          后面你會看到,幾乎所有的類庫都包含這個概念。本質(zhì)上,你可以這么認為,每一個 Field 封裝了與它所包含的輸入框相關(guān)的一切信息。

          3.1 rc-form[1] (Antd Form 3.x[2] )

          3.1.1 背景

          這個表單實際上是大家很熟悉的組件庫 Antd Design Form 3.x 的底層依賴,本質(zhì)上用過 Antd Design Form 的同學(xué)都可以認為是用過這個表單。決定先講這個表單也是基于這一點。

          當然表單本身不算復(fù)雜,特點鮮明,不管是源碼還是暴露的 API 都比較有年代感,一看就能看出是 class component 時代的產(chǎn)物。

          3.1.2 例子

          簡單來看一下官方的例子, 感受下:

          import { Form, Icon, Input, Button } from 'antd';

          function hasErrors(fieldsError{
            return Object.keys(fieldsError).some(field => fieldsError[field]);
          }

          class HorizontalLoginForm extends React.Component {
            componentDidMount() {
              // To disable submit button at the beginning.
              this.props.form.validateFields();
            }

            handleSubmit = e => {
              e.preventDefault();
              this.props.form.validateFields((err, values) => {
                if (!err) {
                  console.log('Received values of form: ', values);
                }
              });
            };

            render() {
              const { getFieldDecorator, getFieldsError, getFieldError, isFieldTouched } = this.props.form;

              // Only show error after a field is touched.
              const usernameError = isFieldTouched('username') && getFieldError('username');
              const passwordError = isFieldTouched('password') && getFieldError('password');
              return (
                <Form layout="inline" onSubmit={this.handleSubmit}>
                  <Form.Item validateStatus={usernameError ? 'error: ''} help={usernameError || ''}>
                    {getFieldDecorator('username', {
                      rules: [{ required: true, message: 'Please input your username!' }],
                    })(
                      <Input
                        prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />
          }
                        placeholder="Username"
                      />,
                    )}
                  </Form.Item>
                  <Form.Item validateStatus={passwordError ? 'error: ''} help={passwordError || ''}>
                    {getFieldDecorator('password', {
                      rules: [{ required: true, message: 'Please input your Password!' }],
                    })(
                      <Input
                        prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />
          }
                        type="password"
                        placeholder="Password"
                      />,
                    )}
                  </Form.Item>
                  <Form.Item>
                    <Button type="primary" htmlType="submit" disabled={hasErrors(getFieldsError())}>
                      Log in
                    </Button>
                  </Form.Item>
                </Form>

              );
            }
          }

          const WrappedHorizontalLoginForm = Form.create({ name'horizontal_login' })(HorizontalLoginForm);

          ReactDOM.render(<WrappedHorizontalLoginForm />, mountNode);

          對應(yīng)的頁面如下圖:

          3.1.3 試跑源碼

          簡單克隆下源碼,nvm 切換到 10.x 下,yarn 然后 yarn start 即可,本身項目自帶 dev server,然后嘗試修改源碼即可立即生效。

          源碼閱讀過程中,有一些不太常用的小知識,希望對大家閱讀源碼有幫助:

          Mixin[3]

          react 早期提供的基于 createReactClass 的邏輯復(fù)用手段,不過官方文檔說問題很多, 在 ES6 class 中甚至直接不支持了,從目的來看是為了復(fù)用一些具體組件類無關(guān)的通用方法。

          hoist-non-react-statics[4]

          用來拷貝 React Class Component 中的靜態(tài)方法。應(yīng)用場景是 Hoc 包裹的時候,很難知道有哪些方法是 React 提供的,哪些是用戶定義的,而這個方法可以方便地把被包裹組件的非 React 靜態(tài)方法拷貝到向外暴露的 HOC 上面(詳見鏈接)[5], 這樣即使用 HOC 包裹過的組件類上的靜態(tài)方法也不會丟失。

          3.1.4 大體思路

          這是我當時閱讀源碼時留下的一張圖, 希望對大家有所幫助。

          3.1.5 整體設(shè)計

          實際上,感覺 rc-form 整體設(shè)計上還是比較簡單的, 從官方的受控組件出發(fā),getDecorator 實際上就是個 HOC,內(nèi)部用 React#cloneElement 將 value & onChange 注入到表單控件上去。通過這種方法,所有的表單控件都被 rc-form 接管了,后續(xù)交互過程中不管是輸入輸出,校驗后的錯誤狀態(tài)信息都被托管在了組件的內(nèi)部,用戶確實被解放了出來。

          美中不足的是,rc-form 使用了一個 forceUpdate 來完成內(nèi)部狀態(tài)與視圖 UI 的同步,這個 forceUpdate 被放在了 Form 組件中,換句話說,任何一個微小的變動 (比如某個字段的輸入) 都將導(dǎo)致整個表單層面的渲染 (我們習(xí)慣稱之為全局渲染),這也就是后來 rc-form 被廣為詬病存在性能問題的根本原因,其本質(zhì)就在于它粗獷的渲染粒度。

          3.2 rc-field-form[6] (Antd Form 4.x[7])

          3.2.1 背景

          基于上述提到的一些缺陷,Antd 4.x 對于表單模塊進行了重新設(shè)計。更具體來說,設(shè)計思路上,渲染粒度的把控從表單級別具化到了組件級別,使得表單性能大幅度提升。源碼中大量使用 React Hooks,同時簡化了暴露的 API, 提升了易用性。

          3.2.2 例子

          可以和上述的代碼實例對比下,最直觀的變化是砍掉了不知所謂的 getFieldDecorator 整體上感覺確實清爽不少。

          import { Form, Input, Button, Checkbox } from 'antd';

          const layout = {
            labelCol: { span8 },
            wrapperCol: { span16 },
          };
          const tailLayout = {
            wrapperCol: { offset8, span: 16 },
          };

          const Demo = () => {
            const onFinish = values => {
              console.log('Success:', values);
            };

            const onFinishFailed = errorInfo => {
              console.log('Failed:', errorInfo);
            };

            return (
              <Form
                {...layout}
                name="basic"
                initialValues={{ remember: true }}
                onFinish={onFinish}
                onFinishFailed={onFinishFailed}
              >

                <Form.Item
                  label="Username"
                  name="username"
                  rules={[{ required: true, message: 'Please input your username!' }]}
                >

                  <Input />
                </Form.Item>

                <Form.Item
                  label="Password"
                  name="password"
                  rules={[{ required: true, message: 'Please input your password!' }]}
                >

                  <Input.Password />
                </Form.Item>

                <Form.Item {...tailLayoutname="remember" valuePropName="checked">
                  <Checkbox>Remember me</Checkbox>
                </Form.Item>

                <Form.Item {...tailLayout}>
                  <Button type="primary" htmlType="submit">
                    Submit
                  </Button>
                </Form.Item>
              </Form>

            );
          };

          ReactDOM.render(<Demo />, mountNode);

          3.2.3 試跑源碼

          簡單克隆下源碼,yarn 然后 yarn start 居然跑步起來,提示我漏裝了 hast-util-is-element,安裝完成后 yarn start 成功啟動了 dev server,然后嘗試修改源碼發(fā)現(xiàn)可立即生效。這是我當時整理的一張大概的流轉(zhuǎn)圖:


          1. 整體的代碼還是比較清晰可讀的,其實這里的想法很簡單,我們知道其實強制 rerender 本質(zhì)上是將最新的 props / state 重新地沿著組件樹往下傳遞,其本質(zhì)也可以理解為一種通訊的方式。只不過這種通訊方法的代價就是導(dǎo)致所有的組件都重新渲染。

          2. 既然在 Form 上無腦地 rerender 將導(dǎo)致性能問題,那么解決的方向一定是盡可能地縮小 rerender 的范圍,如何將最新的表單狀態(tài)僅僅是同步到需要同步的表單項呢? 很自然地,這里用到了訂閱模式模式。

          3. 基于上面的設(shè)計,展開其實就是 2 點:

              a. 每個表單項各自維護自身的狀態(tài)變化,value-onChange 實際只在當前表                        單項上面流傳;
              b. 流轉(zhuǎn)之后通知其他表單項,這時候其他表單項如何知道自己關(guān)心當前變            化的表單項呢? 這里 field-form 引入了 dependencies,shouldUpdate 來                幫助使用者方便地聲明自己所依賴的表單項。

          3.2.4 支持嵌套數(shù)據(jù)結(jié)構(gòu)

          其實在 rc-form 中我漏了一塊比較重要的內(nèi)容沒講,那就是對嵌套數(shù)據(jù)結(jié)構(gòu)的支持,比如我們知道實際上用戶填寫表單最終得到的實際上是一個大 json。對于比較簡單的場景,可能表單的 json 只有一級。

          譬如登錄場景下,比較常見的結(jié)構(gòu):

          {
             phoneNumber'110',    // 手機
             captcha'YxZd' ,       // 圖片校驗碼
             verificationCode'2471',  // 短信驗證碼
          }

          但是實際上在復(fù)雜場景下,很有可能會出現(xiàn)形如:

          {
             companyName'ALBB'
             location'London'
             business: {
                commerce: {
                  income: {
                    curYear110
                    lastYear90
                  }
                },
                data: {
                   dau1
                }
             },
             staffList: [
               {
                  name'zhang3'
                  age'22'
               },
               {
                  name'li3'
                  age'24'
               },
             ]
          }

          這時候, 簡單的 <FormItem name="field1"<Input /><FormItem> 可能就不太夠了,比如這里我們就需要表單項能表單包括對象和列表的嵌套結(jié)構(gòu),我們就勢必需要在表單層面上表示我們所謂的嵌套關(guān)系。通常來講,表單對于嵌套數(shù)據(jù)的支持都是通過表單項的唯一標識 name 上,這里就以 field-form 為例來看:

          import React from "react";
          import ReactDOM from "react-dom";
          import "antd/dist/antd.css";
          import "./index.css";
          import { Form, Input, InputNumber, Button } from "antd";

          const Demo = () => {
            const onFinish = (values) => {
              console.log(values);
            };

            return (
              <Form name="nest-messages" onFinish={onFinish}>
                <Form.Item
                  name={["user", "name"]}
                  label="Name"
                  rules={[{ required: true }]}
                >

                  <Input />
                </Form.Item>
                <Form.Item
                  name={["user", "email"]}
                  label="Email"
                  rules={[{ type: "email" }]}
                >

                  <Input />
                </Form.Item>
                <Form.Item
                  name={["user", "age"]}
                  label="Age"
                  rules={[{ type: "number", min: 0, max: 99 }]}
                >

                  <InputNumber />
                </Form.Item>
                <Form.Item name={["list", 0]} label="address1">
                  <Input />
                </Form.Item>
                <Form.Item name={["list", 1]} label="address2">
                  <Input.TextArea />
                </Form.Item>
                <Form.Item>
                  <Button type="primary" htmlType="submit">
                    Submit
                  </Button>
                </Form.Item>
              </Form>

            );
          };

          ReactDOM.render(<Demo />, document.getElementById("container"));

          可以看到本質(zhì)上就是將數(shù)據(jù)字段的嵌套的信息拍平存儲在了 name 中(此時 name 是數(shù)組),當然更常見的一種做法是形如 lodash get/set 類似的 path 規(guī)則:user.name user.age address[0],只是表現(xiàn)上不同, 本質(zhì)上都是一樣的。關(guān)于為什么 antd 4.x 要使用數(shù)組而不是更常見的做法,官方也給出了解釋:

          In rc-form, we support like user.name to be a name and convert value to { user: { name: 'Bamboo' } }. This makes '.' always be the route of variable, this makes developer have to do additional work if name is real contains a point like app.config.start to be app_config_start and parse back to point when submit.


          Field Form will only trade ['user', 'name'] to be { user: { name: 'Bamboo' } }, and user.name to be { ['user.name']: 'Bamboo' }.

          正如你所理解的一樣,實際在表單的內(nèi)部也確實是這么做的,所以的字段,無論層級,都是被拍平管理的,雖然本質(zhì)上可以認為是一顆樹,但是關(guān)于結(jié)構(gòu)的信息只體現(xiàn)在了 name。例如我們把上面的例子打印出來本質(zhì)上就是:

          其實了解了這一點,再回過頭來看 Antd 4.x 中 form 的一眾 API 中的 NamePath 也不難理解了:

          對于表單項的操作都是接受 NamePath 然后對對應(yīng)的匹配項進行操作,換個維度來理解的話,本質(zhì)上你傳入的 NamePath 就是需要操作節(jié)點的路徑。當然這里稍微有個要注意的點就是當操作的目標節(jié)點為非葉子節(jié)點時,更新需要同步到它的所有子孫上去。

          這一塊實現(xiàn)上并不復(fù)雜,想了解的同學(xué)可以翻翻源碼,看看 valueUtil.ts,F(xiàn)ield.ts#onStoreChange 即可,此處不再贅述。

          3.2.5 動態(tài)增減數(shù)組

          現(xiàn)在有了嵌套數(shù)據(jù)的支持,來看看另一個問題,很多時候我們并不事先知道表單中的某個數(shù)組總共有多少項,比如用戶再輸入他的愛好時,他可以有任意多個愛好,這時候我們就需要使用到動態(tài)增減數(shù)組。

          雖然 antd 4.x 提供了 Form.List 組件來幫助我們很方便地構(gòu)建動態(tài)增減數(shù)組的表單,但是實現(xiàn)上繞不開我們前面所說的嵌套數(shù)據(jù)結(jié)構(gòu)。先看個官方的例子:

          import React from "react";
          import ReactDOM from "react-dom";
          import "antd/dist/antd.css";
          import "./index.css";
          import { Form, Input, Button, Space } from "antd";
          import { MinusCircleOutlined, PlusOutlined } from "@ant-design/icons";

          const Demo = () => {
            return (
              <Form>
                <Form.List name="sights">
                  {(fields, { add, remove }) => (
                    <>
                      {fields.map((field) => (
                        <Space key={field.key} align="baseline">
                          <Form.Item
                            {...field}
                            label="Price"
                            name={[field.name, "price"]}
                            fieldKey={[field.fieldKey, "price"]}
                            rules={[{ required: true, message: "Missing price" }]}
                          >

                            <Input />
                          </Form.Item>

                          <MinusCircleOutlined onClick={() => remove(field.name)} />
                        </Space>
                      ))}

                      <Form.Item>
                        <Button
                          type="dashed"
                          onClick={() =>
           add()}
                          block
                          icon={<PlusOutlined />}
                        >
                          Add sights
                        </Button>
                      </Form.Item>
                    </>
                  )}
                </Form.List>

                <Form.Item>
                  <Button type="primary" htmlType="submit">
                    Submit
                  </Button>
                </Form.Item>

              </Form>
            );
          };

          ReactDOM.render(<Demo /
          >, document.getElementById("container"));

          實際渲染的頁面長這樣:

          關(guān)于這一塊的源碼很簡單我就不多贅述, 感興趣可以看看 rc-field-form#src#List.tsx,本質(zhì)上它要做的事情只有 3 個:
          1. 只是替用戶維護一個可增減的數(shù)組, 數(shù)組中每個對象對應(yīng)表單數(shù)組中的一個節(jié)點;
          2. 當用戶想新增 item 時, 在數(shù)組中創(chuàng)建一個新的 field,并給予一個唯一的 key 同時根據(jù) item 的位置生成 name,同時調(diào)用 onChange 來通知表單對象更新內(nèi)部狀態(tài);
          3. 刪除和新增同理。

          問題: 那么在 antd 中上述每個 field 對象上的三個屬性: fieldKey,key,name 都是干嘛的?

          其中 name 最好理解,對應(yīng)的是每個輸入框在最終提交的數(shù)據(jù)結(jié)構(gòu)中對應(yīng)的位置,key 表示的是該節(jié)點的唯一 id (可做 react loop 中的 key),一直讓我很費解的是 fieldKey,因為事實上在 field-form 的源碼中 field 并沒有包含該值,antd 文檔中也沒有對該值進行特別的解釋。不過最后發(fā)現(xiàn) fieldKey 和 key 本質(zhì)上是同一個東西,因為在 antd#components#Form#FormList 中我發(fā)現(xiàn):

          3.2.6 感想

          其實單單對比 Antd 3.x 以及 Antd 4.x 背后的表單, 我們已經(jīng)可以得出一個有趣的結(jié)論:表單方案的性能問題本質(zhì)是在解決各個表單項與項之間及與表單整體之間的通信問題。這里 antd 4.x 利用訂閱有選擇地通知替代了無腦 rerender 流重繪,實質(zhì)上是更細粒度的組件通訊實現(xiàn)了更小的通訊代價。接下來我們會看看國外幾個類似表單的發(fā)展,恰恰也印證了這個觀點。

          3.3 ------- 國際分割線 --------

          其實國內(nèi)社區(qū)主要還是是以 antd form 為主, 但是實際上,國外 React 社區(qū)的表單方案也有著類似的發(fā)展思路。下面大概過一下,思路上類似的地方就不再展開細講了。

          3.4 Redux-Form

          Redux-form 算是比較早的一款 react 表單方案了,由于 redux 的流行,使用 redux 進行表單的狀態(tài)管理是一個很自然的思路。v6 以前的表單大概是這樣的:


          這里存在兩個問題:

          1. 性能問題:用戶的任何一個按鍵操作都會引發(fā)狀態(tài)的更新而后全局渲染整個表單,可以看到這個問題和我們之前說的 rc-form 存在的問題是類似的。
          2. 依賴 redux:用 redux 來管理表單數(shù)據(jù)流后來被證明是沒有必要的,總的來說就是增大了體積,而且導(dǎo)致很多原本不需要 redux 的項目強制安裝 redux。關(guān)于這個可以看看 Dan 的說法。

          如果你還在猶豫,可以看看:

          Dan 發(fā)表的看法:良好實踐標準[8]


          redux 官網(wǎng)的看法:Should I put form state or other UI state in my store?[9]


          redux-form 作者看法:redux-form[10]

          事實上 redux-form 在 v6 之后更改了更新策略,將 Field 單獨注冊到 Redux,用 Redux 天然的訂閱機制實現(xiàn)了直接對 Field 的更新。可以認為實現(xiàn)了類似 rc-field-form 對于 rc-form 的優(yōu)化。

          3.5 Formik

          3.5.1 背景

          鑒于 Redux-form 強依賴 Redux 所帶來的的問題,F(xiàn)ormik 拋開了 Redux,自己在內(nèi)部維護了一個表單狀態(tài)。大概看了下作者的設(shè)計動機,主要是減少模板代碼&兼顧表單性能。雖然提供了 FastField 來做一定的性能優(yōu)化,不過仍然是以表單整體為視角的粗粒度的狀態(tài)更新,所以本質(zhì)上并沒有逃開全局渲染的,拋開 FastField 來看,它和 rc-field 的設(shè)計思路甚至有些類似。

          3.5.2 例子

          3.5.3 核心思路


          所謂的 FastField 性能優(yōu)化,不過是通過一層 HOC 包裹實際的 Field,然后在這個中間層中用 shouldComponentUpdate 決定當前更新的狀態(tài)是否為該 HOC 包裹的 Field 狀態(tài)。

          可以看出來就是粗暴地對表單中幾個關(guān)鍵的狀態(tài) valueerrortouched 以及傳入的 prop 的長度和 isSubmit 幾個關(guān)鍵字段進行淺比較,如果碰到類似字段 a 決定字段 b 是一個輸入框還是下拉框這種場景,還得自己實現(xiàn) shouldUpdate 的邏輯。所以整體來看可以認為是這樣:


          3.5.4 React DevTools Tips

          在探究這個表單的時候,碰到這里有個很有意思的結(jié)論, 由于只是 FastField 這種外層 connect 接入 context,內(nèi)層 shouldComponentUpdate 做優(yōu)化的機制,這時候通過 devtools 中 highlight updates 并不能看出是否是全局渲染,那玩意在這種情況下會給你誤導(dǎo),這時候更好的方法應(yīng)該是使用插件提供的 profiler,下面這個例子很好地詮釋了這一點:

          Demo 地址:https://codesandbox.io/s/7vhsw



          3.5.5 感想

          總的來看,F(xiàn)ormik 完成了它最初的設(shè)計,從 redux 中解放出來,并一定程度緩解了表單全局渲染導(dǎo)致的性問題,打包體積也很小只有 12.7k,在 redux-form 橫行的年代確實給力(怪不得會被官方推薦)。但是由于發(fā)布的年代早, 對于后續(xù) react hooks 的支持不是十分全面,且底層設(shè)計上還是沒有支持到更細的空間更新粒度,所以當表單膨脹或是聯(lián)動場景較多時,單單靠 FastField 也就力不從心了。

          3.6 React-final-form

          這是 Redux-form 的作者在維護了多年 Redux-form 之后的又一力作,作者的本意是寫一個“無第三方依賴、pure JS、插件式”的表單。在渲染上,final-form 也采取了和 rc-field-form 類似的思路,F(xiàn)orm 作為中心訂閱器,負責(zé)各組件事件的分發(fā),而每個 Field 管理自己的數(shù)據(jù),獨立地訂閱自己感興趣的其他表單項,并獨立地完成渲染。

          Final-form 我沒有細看,只是大概了解了下,感興趣的同學(xué)可以看看 final-form 作者的演講: Next Generation Forms with React Final Form[11]

          3.7 上述五個表單方案的變遷

          其實你會發(fā)現(xiàn)歷史總是驚人地相似,國內(nèi)由于飽受 rc-form 全局渲染的困擾而推出了基于訂閱的 rc-field-form,國外的發(fā)展歷程也是類似的: redux-form(v5) => redux-form(v6)formik => react-final-form。總結(jié)下,整個變遷大概可以這么理解:


          3.8 React-hook-form

          3.8.1 背景

          上面已經(jīng)說了幾個表單,我們可以發(fā)現(xiàn)這些表單都是通過維護了一個 state 來處理表單的狀態(tài),無論是集中更新,還是以訂閱的方式進行分布式更新,它們都是基于這個表單狀態(tài)來完成的,這就是 react 的受控表單模式。

          現(xiàn)在,我們不妨換個思路,從非受控的角度入手,非受控表單就不是使用 state 那一套了,它是通過 ref 來直接拿到表單組件,從而可以直接拿到表單的值,不需要對表單的值進行狀態(tài)維護,這就使得非受控表單可以減少很多不必要的渲染。

          但是非受控表單也存在著它的問題,在動態(tài)校驗、動態(tài)修改(聯(lián)動)方面不是很方便,于是在 react hooks 出現(xiàn)以后誕生了一個以非受控思想為基礎(chǔ)的表單庫 react-hook-form, 這個表單的設(shè)計思路很新奇,完全是擁抱原生, 擁抱非受控. 核心思路是各個組件自身維護各自的 ref, 當校驗、提交等發(fā)生時,便通過這些 ref 來獲取表單項的值。

          3.8.2 簡單例子

          3.8.3 核心思路

          源碼 Commit:1441a0186b8eab5dccb8d85fddb129d6938b994e

          Demo 地址: https://codesandbox.io/s/happy-mccarthy-1nxuq?file=/src/App.js


          這里其實有一些問題,由于所有的 rerender 都是 field 級別而不是 form 級別的 (表單渲染),即:

          1. 性能的優(yōu)化:由于各個字段狀態(tài)由組件自己托管,并不需要數(shù)據(jù)回流,除了這一大塊以外,代碼中也有很多處理很好的細節(jié):

            a. 對錯誤進行淺層比較,例如上一輪渲染已經(jīng)展示了錯誤信息 a, 如果這一輪渲染錯誤信息不變的話, 則不重新渲染.

            b. 表單的內(nèi)部狀態(tài)(isDirty,touched,submitCount,isSubmitting 等等)統(tǒng)一用過 Proxy 包裝, 在初次渲染的時候利用 Proxy 記錄用戶對于各個狀態(tài)的訂閱情況,不訂閱的話變化將被忽略,不引發(fā)重新渲染。

            c. 雖然 watch 默認會觸發(fā)全局渲染,不過 useWatch 可以做到不觸發(fā)全局渲染的情況下通知某個字段的更新,本質(zhì)上是訂閱機制,將 useWatch 調(diào)用方的 state hook 維護在了表單的內(nèi)部對象上,一旦有更新通過這種方式可以做到僅僅通知訂閱組件。

          2. 可惜的是,任何表單下的錯誤信息變化,都會觸發(fā)全局的渲染,這一點感覺不是特別好。至少可以 ErrorMessage 里面可以來個淺層比較。

          3.8.4 動態(tài)校驗以及聯(lián)動

          為了支持動態(tài)校驗,react-hook-form 在進行表單注冊的時候還會將 onChange、onBlur 等事件掛載到表單組件上,保證對與用戶輸入、修改行為的監(jiān)聽,從而可以對表單校驗、表單值監(jiān)聽等進行觸發(fā)。

          非受控表單除了動態(tài)校驗的問題,還存在聯(lián)動實現(xiàn)的問題。由于 react-hook-form 不會將表單的值維護在 state 中,用戶輸入不會觸發(fā)整表層的 JSX 更新,因此 react-hook-form 提供了 watch,以及性能更好的 useWatch,來對于需要進行聯(lián)動的表單進行注冊,當用戶進行修改的時候會調(diào)用更新。(本質(zhì)上這兩個東西和 rc-field-form 中的 dependences / shouldUpdate 目的類似)。

          關(guān)于 watch 和 useWatch

          其實兩者是存在性能差異的,useWatch 可以認為是把更新移到了更局部的位置,所以性能上會更有優(yōu)勢:


          3.8.5 兼容三方 UI 庫

          由于大部分第三方 UI 庫是遵照了 react 受控思想設(shè)計的,例如 Antd#Input 并沒有出 ref,官方也提供了 Controller 組件,用它包裝的三方組件只需要遵循默認的受控規(guī)范 onChange / value 即可,Controller 會在其構(gòu)建內(nèi)部狀態(tài),變相模擬出了 Uncontrolled Component。


          3.8.6 感想

          最終來看 react-hook-form 的其實與其他受控表單庫可以說是殊途同歸,都是希望狀態(tài)分布管理,單個表單項的更新不影響其他的表單項,而聯(lián)動都可以說是使用了訂閱來做到的,從某種程度上看,基于非受控實現(xiàn)的 react-hook-form 來看這一切甚至更加自然。可以看到, 非受控也可以做到任何受控表單能做的事情,這也是為什么我個人在 2.3 小節(jié)中提到, 受控和非受控從某種層面上看可以互相實現(xiàn)

          3.9 橫向比較

          這里再給個橫向比較圖:

          4.  Formily (1.x)

          4.1 背景

          如果不了解之前沒有了解過 Formily 可能下面的內(nèi)容會有些突兀,建議可以先大概了解下。因為這一節(jié)我不打算講 Formily 的基本用法,只是談?wù)勎覍τ?Formily 的一些理解。

          之所以把 Formily 單獨抽出來作為一講來講,是因為在我做表單調(diào)研工作的這個階段 (2020.10),毫不夸張地說,F(xiàn)ormily 是我當時認知范圍內(nèi),設(shè)計理念最先進 (也可以說是激進),表單領(lǐng)域研究最透徹的一個表單解決方案. 在我寫總結(jié)的時候,它的斷代全新版本 2.x 也已經(jīng)算是發(fā)了 preview 版,據(jù)說有挺多改進的, 不過還沒來得及仔細看,所以以下內(nèi)容只針對 1.x。

          4.2 場景

          在我的認知里,雖然 Formily 初衷是大而全,對于場景的預(yù)設(shè)考慮比較完備,但是我感覺它還是比較適合高復(fù)雜度,表單聯(lián)動多,有比較極端的性能要求的表單場景, 如果只是簡單場景確實沒必要,正如我們所說的,antd 4.x 已經(jīng)拜托了全量渲染,如果能合理地利用 dependencies / shouldUpdate 其實性能表現(xiàn)表現(xiàn)上已經(jīng)足夠好。不過如果你的場景特別復(fù)雜,特別是聯(lián)動邏輯比較多,利用 formily 還是能夠有效地幫助你收斂邏輯,降低心智負擔(dān)的。

          4.3 學(xué)習(xí)成本

          雖然我上面說到 Formily 理念先進,但其實業(yè)界對于它可以說是褒貶不一的,被詬病最多的問題就是學(xué)習(xí)成本。我個人也認為這個是阻礙 Formily 火起來的最最核心的問題 (我寫文章的時候 Formily 已經(jīng)有 3000 多個 star 了)。坦率來講, Formily 的學(xué)習(xí)成本相較于社區(qū)其他表單方案還是偏高的,其實原因主要是兩方面的:

          1. 用戶文檔:

            a. 在 Formily 用戶群里面經(jīng)常能看到有用戶反映 Formily 官方文檔訪問速度慢, 甚至打不開, 這一點我個人也時常碰到.

            b. 用戶文檔不夠清晰, 很多地方感覺沒有介紹清楚, 諸多細節(jié)并沒有提到, 需要自己去摸索, 這一點 antd design 簡直是業(yè)界典范. (這一點也不絕對, 畢竟 Formily 可以算是表單垂直領(lǐng)域的高階類庫,antd 以組件庫為核心, 兩者本身的認知門檻也是有差距的)

          2. 整個方案大而全,理念先進,這也導(dǎo)致新的概念比較多,像是 schema 描述結(jié)構(gòu),coolpath 路徑系統(tǒng),effects 甚至引入 rxjs 來做聯(lián)動管理,雖然認真看看會發(fā)現(xiàn)其實所以概念的引入都有一定的道理,不過對大部分開發(fā)者而言,盡可能低的學(xué)習(xí)成本,盡可能高的開發(fā)效率才是他們所追求的,所以 Formily 大量精致的概念對于一些新的用戶來說是非常不友好的。

          4.4 理念

          上面說了 Formily 學(xué)習(xí)成本如此的高,但是我還是非常希望聊一聊,甚至單獨開了一節(jié)來聊 Formily。因為它的設(shè)計理念確確實實代表了算是業(yè)界表單的比較先進的水平。下面講一些我自己印象深刻的點:

          4.4.1 通訊

          4.4.1.1 從數(shù)據(jù)回流到訂閱再到 effects

          業(yè)界的表單一個一個看下來,我自己有個很直觀的感覺,就是各個表單方案歸根結(jié)底,其實是在解決各個表單項之間以及表單項與表單整體的通信問題:

          1. 我們可以看到最開始的 redux form (< 6), rc-form,他們通過全量 rerender 之后新的 props 層層透傳來把信息通知到每個 Field 上,這樣信息的通信效率是很低的,表現(xiàn)上就是表單性能比較差。

          2. 后面 rc-field-form 以及其他一些表單都采取了自己獨立更新,依賴項目走訂閱更新的路子,這本質(zhì)上就是帶來了更好的通信效率及更優(yōu)秀的表單性能。

          3. 此外,無一例外他們也都推崇 onChange + fomrRef 的組合來表達邏輯:

          很自然地, Formily 也是訂閱式的, 但是它表現(xiàn)上更為獨特, 因為它把你的所有的表單相關(guān)的邏輯都收斂到了一個叫做 effects 的字段中, 寫法非常新穎, 即:


          這里的核心在$('event_type', 'path_rule'), 意思是搜索所有 path_rule 命中的表單項,訂閱他們的 event_type 事件,最后整體的返回是一個 rxjs 流。這里我想說幾點:

          1. 首先用 effects 收斂各種表單行為,聯(lián)動邏輯這真是一大創(chuàng)舉,對比其他方案掛在組件上的各種 onChange 回調(diào)散落在整個視圖層,這個收斂對于代碼的可讀性的提高確實有很大幫助。

          2. path_rule 必須要遵循作者自己實現(xiàn)的一套 DSL, 即 cool-path[12],感覺出發(fā)點是好的,目的是既可以精準定位到某個具體的表單項,又有能力根據(jù)情況做到批量處理,批量訂閱。這其實也可以算是一大創(chuàng)新,它讓賦予了 Formily 極強的聯(lián)動表達能力,一對一,一對多,多對多都能很好的表達。但壞就壞在文檔不全,偏偏它的語法還有點四不像,既不是路徑系統(tǒng)也不是 glob pattern,其實說實話我用了挺久了有時候還是用不明白。

          3. 這里引入 rxjs,確實很 geek,不過就我自己而言感覺所有的場景都只是當成簡單的事件訂閱在用。

          4.4.1.2 關(guān)于 react-eva

          其實這套寫法并不是空穴來風(fēng),effects + actions + rxjs 這套組合也是作者自創(chuàng)的,為此專門還專門抽象了一個庫,叫 react-eva[13],想法很明確,即圍繞 rxjs 針對 react 組件做的一個內(nèi)外通訊方案:

          這套方案有 2 個核心優(yōu)勢:

          1. 聯(lián)動性能好,遵循這個規(guī)范很自然而然地就不需要在視圖層使用 hooks 了。
          2. 提高了代碼可維護性,所有的邏輯收攏到了 effects 中。
          4.4.1.3 從 “Effect 只執(zhí)行一次” 談核心理解

          之前有同事問我這樣一個問題:為什么明明我的某個 react hook 已經(jīng)更新了但是在最新的 effect 的某個 observer 中還是舊的值?

          其實大概多試驗幾次就會發(fā)現(xiàn),effects 函數(shù)本身只會執(zhí)行一次。這是表面原因,不過我理解更深層次的原因是, 作者不希望讓 effects(可以認為是你申明的聯(lián)動邏輯) 和 當前視圖層的 hooks 互相依賴,這里有兩方面原因:

          1. effects 應(yīng)該是是比較純粹的東西,類似 reducer, 甚至可抽離和復(fù)用。
          2. 另一方面,如果 effects 和 視圖層 hooks 有耦合,意味著你每次需要 effects 重新執(zhí)行的話就需要 setState,不知不覺又變成了整表重繪,這可以看做是一種性能倒退,顯然是作者不希望看到的。

          所以我視角里的 Formily 其實沒那么 React, 它更像是一個自動機,你寫的 JSX / Schema 不過是在聲明這個表單的結(jié)構(gòu),一旦完成首次渲染,這個表單完全可以通過 用戶交互 + 開發(fā)者預(yù)先定義的 effects 完成表單閉環(huán). 它的生命周期更像是自己獨立的而不是屬于包裹它的 React 組件容器。這一點在 “Formily 的數(shù)據(jù)模型是表單 UI 的完全表達”中也得到了了印證。

          4.4.2 底層數(shù)據(jù)模型

          4.4.2.1 可訂閱模型作為基類

          剛在說到表單中所有的事件都可以通過 effects 聲明并監(jiān)聽,事實上,這一點在表單內(nèi)部仍然成立。可以這么說, 整個 Formily 不論內(nèi)外,都是一個基于一個簡單的思想,一切結(jié)構(gòu)可訂閱,譬如他的類繼承模型如下:

          4.4.2.2 表單 = 樹狀結(jié)構(gòu)組織的一堆字段

          另一個令我印象深刻的點在于,F(xiàn)ormily 底層的數(shù)據(jù)模型是它整個表單的完整表達。在看其他表單方案的時候,雖然也能看到每個表單的內(nèi)部用形如 store,state 的狀態(tài),里面存的無非是一些 valueinitialValue,rules,dirty,touched 等等之類的一些狀態(tài)相關(guān)的信息。然后對于 Formily:

          Formily 不但包括了這些,它甚至把某個字段的輸入組件上被傳入的 props,當前表單的掛載狀態(tài),顯示狀態(tài)都給描述出來了。這里反映出幾點:

          1. 整個表單的狀態(tài)能完整地表達表單的 UI,這意味我們在 effects 中操作表單時獲得了極高的自由度,你甚至通過設(shè)置某個字段的 state 來控制這個字段的下拉列表, 單純地修改數(shù)據(jù)更新視圖,非常純粹。換作其他表單,你大概率需要在 JSX 中寫三元表達式或是有了個 useState 的 hooks 專門用來操作視圖層。

          2. 這里狀態(tài)的完整表達意味著,F(xiàn)ormily 跳脫出了 React 視圖框架的禁錮,它是完完整整的內(nèi)核,你可以通過它完整地驅(qū)動任意一個視圖框架,例如 Vue。其他有一些表單其實多多少有一些視圖層的信息是遺留在 React 的 UI 組件上的。

          事實上,不但是具體的 field,如果把視野拉高,你會發(fā)現(xiàn)整個表單都可以用一個樹狀的結(jié)構(gòu)來進行完全表達:

          仔細想想,這其實是很自然的,因為在有嵌套數(shù)據(jù)的場景下,表單的天然結(jié)構(gòu)就是一棵樹。可以說,這顆樹就是表單的完全表達,結(jié)合之前的 effects,表單所有的交互都能在 effects 內(nèi)部閉環(huán),因為 Formily 的數(shù)據(jù)層是可以做到 UI 層的完全表達的。

          回到我們再 4.4.1.3 小節(jié)中說的那個問題,你可以看到,其實 Formily 的狀態(tài)流轉(zhuǎn)應(yīng)該是很 Redux 的:

          它并不需要依賴任何 React 相關(guān)的 hooks/callback 就能實現(xiàn)自己的鏈路閉環(huán),整體的鏈路我大概理了下:

          4.4.2.3 Immer

          不過,剛才說到 effects 的 state 可以非常自由地操作表單/表單項的任意屬性。這帶來了極高的自由度,確實提高了庫的使用者的使用體驗,不過工作量并不會憑空消失,對于 Formily 的開發(fā)者而言,用戶只管設(shè)置,那他們就需要對每一種情況進行兜底,比如用戶只是簡單地在 effects 中通過 state 重新設(shè)置了 rules,那 Formily 底層就得考慮重新校驗,表單錯誤狀態(tài)的更新等等. 那作者如何知道我們在回調(diào)中修改了哪個屬性呢?

          答案是 Immer。第一次看到確實是大呼牛逼,因為在我淺薄的認知里面,Immer 存在的價值僅僅是 “寫 redux reducer 的時候便捷地創(chuàng)建不可變對象”,但是作者居然想到了利用 immer 中的 patches 來記錄用戶的操作記錄,用在這里非常自然,整個思路大概如下:

          這里確實非常精彩,因為涉及到比較函數(shù)前后對比兩個對象的變化,第一想法都是 “臟檢查”,作者很巧妙地繞開了臟檢查。不過我當時的疑問是:里使用了 immer 性能開銷真的會更小嗎?

          為此我專門去研究了下 immer 的原理. 簡單來說 Immer 的高性能基于這樣的一個事實:

          新建/拷貝對象的開銷高,引用的開銷低。

          所以為了更好的性能應(yīng)該盡可能少地新建對象,這個背景下,immer 實現(xiàn)了 Copy on write 的效果:

          當然,對于小表單這點性能優(yōu)化可有可無,不過確實可以看出來 Formily 對性能是有極度壓榨的。

          4.4.3 Schema 與 Low / No Code

          4.4.3.1 概念

          Formily 最頂層還有個叫 Schema 的概念,為什么要有 Schema 呢?為了更好地解釋 Schema,先聊聊什么是 Low / No Code。

          表單方向的 Low / No Code,通俗來講就是表單可視化搭建,產(chǎn)品形態(tài)就是各種表單生成器。不過對于 Low / No Code,Wiki 上有更準確的定義:

          A low-code development platform (LCDP) is software that provides a development environment used to create application software through graphical user interfaces and configuration instead of traditional hand-coded computer programming.

          No-code development platform (NCDPs) allows programmers and non-programmers to create application software through graphical user interfaces and configuration instead of traditional computer programming.

          它的優(yōu)勢也很明顯,那就是降低開發(fā)門檻,軟件開發(fā)的工作不再局限于專業(yè)的技術(shù)人員。業(yè)界所謂的前端賦能,形態(tài)之一就是構(gòu)建 No / Low Code 平臺,把需求交給非前端來做.

          4.4.3.2 產(chǎn)品與實現(xiàn)

          包括 Formily 在內(nèi),市面上其實也已經(jīng)有了一些表單生成器,譬如:

          基本上所有的表單 No / Low Code 方案,都離不開一個核心的概念,即 DSL。DSL 可以認為是 UI 視圖與用戶交互之間的橋梁。通常來說這類產(chǎn)品的流程大概是這樣:

          1. 用戶通過在某些非編碼的方式(比如在平臺上拖拽配置)生產(chǎn) DSL;
          2. DSL 通過映射規(guī)則轉(zhuǎn)化為視圖。

          所以 DSL 是媒介,實際上它是抽象模型的代碼表達,譬如:

          用戶可以不知道啥是 select,input 但是他知道什么是下拉框輸入框;

          用戶可以不知道啥是相對定位絕對定位, 但是他知道自己要讓框框再往左邊一點;

          用戶可以不知道啥是 required 但是他會告訴你我希望表單的這一項必填。

          下面是 Formily 通過擴充 JSON Schema 定義的 Schema, 也可以認為是一種 DSL:

          對于 Formily 而言,其同時存在 3 種等效的表達方式, 見:https://formilyjs.org/#/0yTeT0/8MsesjHa,最貼近配置的 JSON Schema,然后是 JSX Schema,最后是最貼近 React 的純 JSX。我們之前也說過, Formily 的 JSX 層更像是在聲明一堆表單結(jié)構(gòu)而不是視圖,其底層邏輯就在這里。它必須保證你使用任何一種表達方式最終呈現(xiàn)出的表單是一致的。

          Formily 引入 Schema 是有一個偉大的遠景 -- 即后續(xù)的表單可以由機器生產(chǎn)或者可視化平臺配置完成。不過從 react-schema-editor[14] 來看的話,暫時完成度還是比較有限的. 我自己業(yè)務(wù)中其實 Schema 也接觸的比較少,從某種程度來講,對于不需要考慮配置/機器生成表單的場景,其實這一層反而有點成為了我們的心智負擔(dān)。我個人感覺 Formily 接地氣的部分還是它的 React 層 (core 層) + Antd 橋接層。

          4.4.3.3 現(xiàn)狀

          類似這樣希望提供可視化解決方案的類庫,F(xiàn)ormily 并不是第一個,不過大多數(shù)感覺一直接受度不高,我自己的理解有幾方面:

          1. 大部分此類平臺是用來賦能非前端同學(xué)的,其實配置過程(也就是開發(fā)過程)并不難,難得是如何平衡產(chǎn)品靈活性與易用性之間的關(guān)系,這里搞不好橫容易陷入“研發(fā)不想用, 非技術(shù)用戶不會用”的窘境。
          2. 另一方面,大部分解決方案只解決了業(yè)務(wù)需求的第一步,開發(fā),這個往往也是最簡單的。但是如何擴充整個鏈路,把產(chǎn)品的整個生命周期考慮在內(nèi),即這種新的開發(fā)形態(tài)下的后續(xù)的 debug 優(yōu)化改進是否也可以做到研發(fā)同學(xué) 0 參與?我感覺當前還不是很成熟,這里面還需要更多探索。

          4.4.4 其他

          其實 Formily 我接觸稍微多一些的就是上面一些個概念,其他的譬如樣式布局我們業(yè)務(wù)用得確實會比較少一點,也了解不多就不誤人子弟了。雖然是說 Formily 的用戶文檔不太好,不過 Formily 理念性的介紹文章還是寫地非常好的,如果大家想對 Formily 有深入了解的話,這里推薦下官方團隊的 Formily 知乎專欄[15]

          對于 Formily,客觀來講,他們算是表單領(lǐng)域的探索者, 雖然不盡完美,有這樣那樣的問題,但是瑕不掩瑜,從中我看到了中國開發(fā)者的偉大智慧,也給了我許多的啟發(fā)。對于想要深入了解表單的同學(xué),我個人認為 Formily 確實是個不錯的學(xué)習(xí)對象。

          5.  結(jié)尾

          關(guān)于表單技術(shù)的一些想法都已經(jīng)散落在文章的各個角落里了,到了總結(jié)的時候反而感覺沒啥可說的。行文倉促,此外文章整體也比較主觀,有些說法可能拿捏可能也沒那么到位,如果有說的不對的地方,歡迎隨時交流指正。

          6.  招聘硬廣告

          急招!我們是字節(jié)跳動游戲前端團隊,團隊當前業(yè)務(wù)包括數(shù)個 DAU 千萬量級的游戲中心化平臺,覆蓋今日頭條、抖音、西瓜視頻等等多個字節(jié)跳動宿主端;還有月流水千萬級的創(chuàng)作者服務(wù)平臺,是抖音官方最重要的游戲短視頻分發(fā)平臺和達人變現(xiàn)渠道。團隊負責(zé)了這些項目產(chǎn)品,以及與之相關(guān)的運營后臺、廣告主服務(wù)平臺、效率工具的前端研發(fā),業(yè)務(wù)技術(shù)包括小程序、H5、Node 等多個方向,且在業(yè)務(wù)快速發(fā)展的同時,還在持續(xù)豐富技術(shù)支持場景。

          簡歷投遞歡迎點擊閱讀原文,檢索「前端開發(fā)(高級)工程師-游戲中臺」或者直接郵箱:[email protected]

          7.  參考鏈接

          [1] https://github.com/react-component/form

          [2] https://3x.ant.design/components/form-cn/
          [3] https://reactjs.org/docs/react-without-es6.html#mixins
          [4] https://github.com/mridgway/hoist-non-react-statics
          [5] https://reactjs.org/docs/higher-order-components.html#static-methods-must-be-copied-over
          [6] https://github.com/react-component/field-form
          [7] https://ant.design/components/form-cn/
          [8] https://github.com/reduxjs/redux/issues/1287#issuecomment-175351978
          [9] https://redux.js.org/faq/organizing-state#should-i-put-form-state-or-other-ui-state-in-my-store
          [10] https://github.com/redux-form/redux-form#%EF%B8%8F-attention-%EF%B8%8F
          [11] rm: https://www.youtube.com/watch?v=WoSzy-4mviQ&feature=emb_logo

          [12] https://github.com/janryWang/cool-path
          [13] https://github.com/janryWang/react-eva
          [14] https://github.com/alibaba/formily/tree/master/packages/react-schema-editor
          [15] https://www.zhihu.com/column/uform




          瀏覽 58
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  天天干天天干素人 | 人人综合 | 北条麻妃精品99青青久久 | 亚洲无人区码一码二码 | 久操91 |