在前端平常的业务中, 无论是官网, 展示页还是后台运营系统都离不开表单, 它承载了大部分的数据采集工作. 所以如何更好地实现它, 是平常工作中的一个重要问题.
在应用 vue 框架去开发业务时, 会将页面上每个独立的可视 / 可交互区域拆分为一个组件, 再通过多个组件的自由组合来组成新的页面. 例如
- <template>
- <header></header>
- ...
- <content></content>
- ...
- <footer></footer>
- </template>
当用户的某个行为触发表单时 (例如注册, 建立内容等), 期望在页面中弹出一个 From 组件. 通常的做法是在 template 中填入一个 < form > 组件用于开发, 并通过控制 data 中的 UI.isOpen 来对其 display 进行控制, 例如在当前 < template > 组件内开发
- <template>
- <header></header>
- ...
- <content></content>
- ...
- <footer></footer>
- ...
- <register-form v-if="UI.isOpen">
- <form-item></form-item>
- ...
- <submit-button></submit-button>
- </register-form>
- </template>
Form 组件与其父组件之间可以通过 prop 以及 $emit 方便通信. 但是也会有以下几个缺陷:
当前组件的 data 必须要有 UI.isOpen 来控制表单, 如果存在多个表单时, 就会有大量的状态来维护表单的开关;
如果表单多次弹出时, 可能需要对表单的 data 进行重置;
与组件化思想相违背, 表单不属于当前页面, 它只是由于用户行为触发的结果.
为了解决以上缺陷, 并且还能具备方便通信的优势, 本文选择用 Vue.extend 将原有 < form > 组件转化为 method function, 并维护在当前组件的 method 中, 当用户触发时, 在页面中挂载, 关闭时自动注销.
实例
App 组件
- <template>
- <div id="app">
- <el-button
- type="primary" icon="el-icon-edit-outline"
- @click="handleClick"
> 注册 </el-button>
- </div>
- </template>
- <script>
- import register from './components/register'
- import { transform } from './transform'
- export default {
- name: 'App',
- methods: {
- register: transform(register),
- handleClick () {
- this.register({
- propsData: { name: '皮鞋' },
- done: name => alert(`${name} 牛 B`)
- })// 欢迎加入前端全栈开发交流圈一起吹水聊天学习交流: 864305860
- }
- }
- }
- </script>
当 < el-button > 的点击事件触发时, 调用 register 方法, 将表单组件挂载在页面中.
Form 组件
- <template>
- <div class="mock" v-if="isVisible">
- <div class="form-wrapper">
- <i class="el-icon-close close-btn" @click.stop="close"></i>
- ...<header />
- ...<content />
- <div class="footer">
- <el-button
- type="primary"
- @click="handleClick"
> 确定 </el-button>
- <el-button
- type="primary"
- @click="handleClick"
> 取消 </el-button>
- </div>
- </div>
- </div>
- </template>
- <script>
- export default {
- porps: { ... },
- data () {
- return {
- isVisible: true
- }
- },
- watch: {
- isVisible (newValue) {
- if (!newValue) {
- this.destroyElement()
- }
- }
- },
- methods: {
- handleClick ({ type }) {
- const handler = {
- close: () => this.close()
- }// 欢迎加入前端全栈开发交流圈一起吹水聊天学习交流: 864305860
- },
- destroyElement () {
- this.$destroy()
- },
- close () {
- this.isVisible = false
- }
- },
- mounted () {
- document.body.appendChild(this.$el)
- },
- destroyed () {
- this.$el.parentNode.removeChild(this.$el)
- }
- }
- </script>
在 App 组件内并未维护 < form > 组件的状态, 其打开或关闭只维护在自身的 data 中.
原理
上述代码中, 最为关键的一步就是 transform 函数, 它将原有的 ` 从 single-file components 转化为了 method function, 其原理如下
- const transform = (component) => {
- const _constructor = Vue.extend(component)
- return function (options = {}) {
- const {
- propsData
- } = options
- let instance = new _constructor({
- propsData
- }).$mount(document.createElement('div'))
- return instance
- }
- }
首先利用 Vue.extend(options) 创建一个 < Form/> 组件的子类
const _constructor = Vue.extend(component)
然后 return function, 它的功能是:
将 < form /> 组件转化为 method
在 method 调用时, 将组件实例化并传递 propsData
- const {
- propsData
- } = options
- let instance = new _constructor({
- propsData
- }).$mount(document.createElement('div'))
为了能够控制实例化后的组件, 选择 instance 返回.
当组件实例化时, 它只是挂载到 document.createElement('div') 上, 但是并没有挂载到页面上, 所以需要将其 appendChild 到页面中. 为了更好的语义化, 选择在组件的生命周期中完成它在页面中的挂载. 实例化时, 会触发组件 mounted 生命周期, 所以当其触发时可以挂载在 document.body 中, 具体如下
- mounted () {
- document.body.appendChild(this.$el)
- }
有了挂载, 就必须要有注销. 对应的生命周期应该是 destroyed, 所以
- method: {
- destroyElement () {
- this.$destroy()
- }
- },
- destroyed () {
- this.$el.parentNode.removeChild(this.$el)
- }
组件注销的时间与它在页面中显示息息相关, 当 < form /> 在页面中不可见时候, 需要注销它
- method: {
- destroyElement () {
- this.$destroy()
- }
- },
- destroyed () {
- this.$el.parentNode.removeChild(this.$el)
- }
一般 Form 组件有两个功能:
done: 代表用户确认;
cancel: 代表用户取消;
当 done 或 cancel 触发时, App 组件内可能会有相应的变化, 所以在组件实例化之后, 利用 $on 去监听对应的 done 事件以及 cancel 事件.
- done && inlineListen({
- method: 'done',
- options,
- instance
- })// 欢迎加入前端全栈开发交流圈一起吹水聊天学习交流: 864305860
- cancel && inlineListen({
- method: 'cancel',
- options,
- instance
- })
其中 inlineListen 函数可以方便后续添加其他的 event, 其代码为
- const inlineListen = ({
- method,
- options,
- instance
- }) => {
- let listener = `on${method}`
- instance[listener] = options[method]
- instance.$on(method, function (data) {
- this[listener](data)
- })
- }
也可以将上述方案封装成 Promise 形式, 如下
- export const transform = (component) => {
- const _constructor = Vue.extend(component)
- return function (options = {}) {
- const {
- propsData
- } = options
- return new Promise((resolve, reject) => {
- let instance = new _constructor({
- propsData
- }).$mount(document.createElement('div'))
- instance.$on('done', data => resolve(data))
- })
- }
- }// 帮助突破技术瓶颈, 提升思维能力
使用
可以将上述属于 < Form/> 公有的 data 以及 method 独立出来, 再通过 mixins 引入到每个表单内, 例如
- export default {
- data() {
- return {
- visible: true
- }
- },
- watch: {
- visible(newValue) {
- if (!newValue) {
- this.destroyElement()
- }
- }
- },
- mounted() {
- document.body.appendChild(this.$el)
- },
- destroyed() {
- this.$el.parentNode.removeChild(this.$el)
- },
- methods: {
- destroyElement() {
- this.$destroy()
- },
- close() {
- this.visible = false
- }
- }// 面向 1-3 年前端人员
- }// 帮助突破技术瓶颈, 提升思维能力
再通过 mixins 混入.
- <script>
- import popupWin from '../mixins/popup-win'
- export default {
- mixins: [popupWin],
- data () {
- return {
- input: '',
- gender: 1
- }
- },
- methods: {
- handleClick ({ type }) {
- const handler = {
- close: () => this.close(),
- confirm: () => {
- const { input } = this
- this.$emit('done', input)
- }
- }
- }// 面向 1-3 年前端人员
- }
- }// 帮助突破技术瓶颈, 提升思维能力
- </script>
调用时, 只需
- export default {
- name: 'App',
- methods: {
- register: transform(register),
- handleClick () {
- this.register({
- propsData: {
- ...
- },
- // done: data => function
- done () {
- // 外部关闭
- this.close()
- }
- })
- }// 面向 1-3 年前端人员
- }
- }// 帮助突破技术瓶颈, 提升思维能力
PS: 如果业务场景需要, 在外部控制表单的关闭时, 只需要改变 done function 的 context, 也就是 this 指针指向 < Form/>.
总结
通过上述的 transform 函数, 将原有的注入式组件转化为了命令式, 简化了页面状态的维护, 在通过 mixins 混入公有 data 以及 method, 简化了表单组件开发. 上述方法也可用于开发 toast,alert,confirm 等组件, 只需要将
Vue.prototype.method = transform(Toast-Component)
结语
感谢您的观看, 如有不足之处, 欢迎批评指正.
来源: http://www.qdfuns.com/article/51117/d237c0b5a6f5f86461d72039ce33f9ca.html