组件自带数据管理,让开发交互更友好

523 阅读4分钟

组件自带数据管理

傻瓜化组件,不写代码,组件也能进行表单交互。这样开发是不是很 nice 🎉。hooks 简单就能实现,听我娓娓道来。

🗒️ 组件数据受控与非受控

不需要通过数据传入,组件就拥有自己的数据。这压根就是 非受控组件,是的 🙃🙃🙃

组件代码大概长这样:

// components/test.jsx
import React, { useState } from 'react'
import { Button } from 'antd'
export default () => {
  const [state, setState] = useState(0)
  return (
    <div>
      <p>{state}</p>
      <Button
        onClick={() => {
          setState(state + 1)
        }}
      >
        点击加一
      </Button>
    </div>
  )
}

使用非受控组件,啥也不用传,组件自己自带数据,能自己和用户交互。

import React from 'react'
import TT from 'components/test'
export default props => {
  return <Test />
}

受控组件尴尬的点就是,组件外没办法操作组件的数据,也没办法获取数据,组件数据只能自生自灭。那如果改成受控组件呢,那代码可能就长这样了。

import React, { useState } from 'react'
import { Button } from 'antd'
export default props => {
  const { state, setState } = props
  return (
    <>
      <p>{state}</p>
      <Button
        onClick={() => {
          setState(state + 1)
        }}
      >
        点击加一
      </Button>
    </>
  )
}

使用受控组件,那就需要自己在父组件做数据状态管理了。

import React from 'react'
import TT from 'components/test'
export default props => {
  const [state, setState] = useState(0)
  return <Test state={state} setState={setState} />
}

那到底是用受控组件还是非受控组件呢,使用非受控组件,对数据没办法获取、干预组件数据。用受控组件呢,父组件还得自己维护数据管理,麻烦。还离题了 👎👎,扯犊子的组件自带数据管理 💣💣💣。那么本文就是为了给出完美答案 👏👏,让组件既能受控,又不需要父组件管理数据。

组件交互设计

目的就是组件不需要做数据管理,但又能操作数据。先参考一波 antd4.0 表单的交互设计

思路:通过 Form.useForm 对表单数据域进行交互。

import React from 'react'
import { Form, Button } from 'antd'
export default () => {
  const [form] = Form.useForm()
  return (
    <div>
      <Form form={form}>
        <Form.Item name="note" label="Note">
          <Input />
        </Form.Item>
      </Form>
      <Button
        type="primary"
        onClick={() => {
          message.info(form.getFieldsValue('note'))
        }}
      >
        查看数据
      </Button>
      <Button
        type="primary"
        onClick={() => {
          form.setFieldsValue({
            note: 'Hello world!'
          })
        }}
      >
        修改数据
      </Button>
    </div>
  )
}


**分析一波:**Form 里面自带数据管理,类似上面的受控组件。外层(父级组件)想操作组件数据,则通过 Form.useForm()实现,如getFieldsValuesetFieldsValue

设计实现

一份分析猛如虎,antd4 表单数据交互是基于rc-form实现的,那如果让我们写一个中台组件,我们怎么实现这个逻辑呢,小组件总不能引一个大包吧。当然是自己动手写,就几行代码就搞定了。来人,上代码 🏇🏇。

先介绍怎么使用,懂得用才容易懂得实现,假设组件EditableTags 就是我们要开发的中台组件,功能是对 tag 标签的增删。

import React from 'react'
import EditableTags,{ useTags } from './tags'
import { Button } from 'antd'
export default () => {
  let tagsHook = useTags()
  return (
    <div>
      <EditableTags
        defaultValue={[
          '第一',
          '第二',
          '第三',
        ]}
        tagsHook={tagsHook} //用于获取数据,当然也可以用onChange获取
      />
      <Button
        onClick={() => {
          console.log("tags", tagsHook.getTags())
        }}
      >
        查看
      </Button>
    </div>
  )
}

EditableTags 组件部分代码

**_store:**存储组件数据,所有组件的数据都存在这个 store

tagsHook: 外层 useTags 传进来的函数,用法同 antd4 form

import { tagsInit, _store } from './store'

export default () => {
  const [tags, setTags, index] = tagsInit(defaultValue)
  tagsHook && tagsHook(index)
  return <div>...</div>
}

./store 的代码

核心部分是./store 的代码。

_store

**解析:**返回一个函数,形成闭包。主要是为了保留这个 index 索引,确保每个组件都有自己唯一索引,_store 是所有组件的数据。

function useTags() {
  let index = null //  组件调用时候 会进行赋值
  let func = i => {
    index = i
  }

  func.getTags = () => {
    if (index === null) {
      throw new Error('your must set useTags to a Tags components property ')
    }

    return _store[index]
  }
  func.setTags = data => {
    if (index === null) {
      throw new Error('your must set useTags to a Tags components property ')
    }
    _store[index] = data
  }

  return func
}

tagsInit

每个组件数据初始化,则是通过 tagsInit

import { useEffect, useState, useRef } from 'react'

const _store = {}
let _length = 0
function tagsInit(defaultValue) {
  let index = useRef(_length++).current

  const [data, setData] = useState(_store[index] || defaultValue || [])

  const setTags = data => {
    setData(data)
    _store[index] = data
  }

  useEffect(() => {
    _store[index] = data
    //删除该元素 释放内存
    return () => {
      delete _store[index]
    }
  }, [])

  return [data, setTags, index]
}

是不是很简单。tagsInit+_store 合起来就是store文件了

✨ 最后

写好中台组件交互,后端能也得心应手开发前端业务。设计好中台组件交互很关键哦。 字节跳动急召:工资高,福利顶。欢迎简历投递:xiaoyanhui@bytedance.com