前端组件化开发指南(二)

如何写出高质量的前端代码》学习笔记

书接上文《前端组件化开发指南(一)》,这篇我们聊聊如何提升组件的可读性和正交性

HOW如何写出好组件?

4. 可读性

组件命名

  • 组件命名应能体现其功能。例如,AddUserDialog表示一个用于添加用户的弹窗组件。

顶部注释

  • 在组件顶部添加注释,说明组件的作用、适用场景和注意事项。

结构化开发

  • 组件应围绕主线任务展开,非主线任务应封装成子组件或工具库。
  • 例如,添加用户弹窗的主线任务包括初始化表单数据、配置校验规则和请求API。至于用户头像上传、裁剪、接口调用等信息,不属于主线任务,不应该放在这个组件内部。

显式修改数据

  • 子组件不应修改父组件传递的 **props****数据。**应通过事件抛出数据变更。

⚠️错误示例 ,父组件中给子组件传值,子组件修改了父组件的值。如何解决?必须查看所有子组件才能读懂该组件的逻辑,难以轻易理解。

html 复制代码
<template>
    <div>
        <child1 :data="formData" />
        <child2 :data="formData" />
    </div>
</template>
<script>
export default {
    data(){
        return {
            formData:{
                key1: {},
                key2: {}
            }
        }
    }
}
</script>

子组件不能修改父组件传递过来的props数据,这应该是组件开发的一个重要原则。更好的方式就是子组件对外抛出事件。

html 复制代码
<template>
    <div>
        <child1 :data="formData" @change="child1Change"/>
        <child2 :data="formData" @change="child2Change"/>
    </div>
</template>
<script>
export default {
    data(){
        return {
            formData:{
                key1: {},
                key2: {}
            }
        }
    },
    methods:{
        child1Change(value){
            this.formData.key1 = value;
        },
        child2Change(value){
            this.formData.key2 = value;
        }
    }
}
</script>

区分元数据和派生数据

元数据:是指组件中最基本的、不可或缺的数据。这些数据通常是直接从外部获取的,或者是用户输入的,代表了组件的当前状态。元数据是组件逻辑的基础,其他数据通常是基于这些元数据计算或派生出来的。比如说代码中的userList。
派生数据:派生数据是基于元数据计算得出的数据。它们通常是元数据的某种变体或总结,帮助简化组件的逻辑和视图渲染。派生数据不需要单独存储,因为它们可以在需要时通过计算得到。比如说代码中的totalCount和vipCount,它们是由userList计算出来的。

比如现在有个用户列表页,展示用户列表,总人数和vip人数,没有使用计算属性:

html 复制代码
<script>
export default {
    data(){
        return {
            userList: [],
            totalCount: 0,
            vipCount: 0
        }
    },
    methods:{
        init(){
            this.userList = [
                {id:1, name: 'zhangsan', isVip: true}, 
                {id: 2, name: 'lisi', isVip: false}
            ];
            this.totalCount = this.userList.length;
            this.vipCount = this.userList.filter(item => item.isVip).length;
        },
        updateUserList(){
            this.userList = [
                {id:1, name: 'zhangsan', isVip: true},
                {id: 2, name: 'lisi', isVip: false}
            ];
            this.totalCount = this.userList.length;
            this.vipCount = this.userList.filter(item => item.isVip).length;
        }
    }
}
</script>

对元数据和派生数据不加区分会导致逻辑繁琐/重复,同时变更元数据需要同步修改其他数据。如果忘记修改,会出现bug。

可以将他们改造为计算属性:

html 复制代码
<script>
export default {
    data(){
        return {
            userList: []
        }
    },
    computed:{
        totalCount(){
            return this.userList.length;
        },
        vipCount(){
            return this.userList.filter(item => item.isVip).length;
        }
    }
}
</script>

使用计算属性来处理派生数据有以下几个优点:

  1. 自动更新:

    • 计算属性会自动追踪其依赖的元数据,当元数据发生变化时,计算属性会自动重新计算。这减少了手动更新派生数据的需要,降低了出错的可能性。
  2. 避免冗余:

    • 通过计算属性,派生数据的计算逻辑被集中在一个地方,代码更简洁,逻辑更清晰。不需要在多个地方手动更新派生数据,减少了代码重复和潜在的同步问题。
  3. 提高可读性:

    • 计算属性的定义通常很直观,能清晰地展示派生数据是如何从元数据中得出的,提升了代码的可读性。

不要滥用watch

仅在需要副作用时使用watch,其他情况应使用计算属性。

还是上面这个例子,如果使用watch代替computed

html 复制代码
<script>
export default {
    data(){
        return {
            userList: [],
            totalCount: 0,
            vipCount: 0
        }
    },
    watch:{
        userList(){
            this.totalCount = this.userList.length;
            this.vipCount =this.userList.filter(item => item.isVip).length;
        }
    },
    methods:{
        init(){
            this.userList = [];
        },
        updateUserList(userList){
            this.userList = userList;
        }
        //其他逻辑
    }
}
</script>

虽然可以看到 totalCountvipCount 的变化逻辑,但你能确定只有在这个 watch 中会修改它们吗?如果其他方法也修改了这些数据,就会出现问题。而计算属性是只读的,不会被其他方法修改。此外,使用 watch 还需要关注数据的初始化值和是否立即执行的问题。

5. 正交性

正交性在组件设计中指的是组件之间的低耦合性。低耦合性意味着组件可以独立于其他组件进行修改和测试,从而提高系统的可维护性和可扩展性。

父组件耦合子组件

父组件与子组件的通信应通过props、事件和方法进行,避免直接访问子组件的内部状态或DOM。错误示例:

a. 访问组件的内部状态

html 复制代码
<template>
    <MyButton ref="button">按钮</MyButton>
</template>
<script>
export default {
    mounted(){
        // 错误用法:不应直接操作子组件的内部数据
        this.$refs['button'].color = 'red';
    }
}
</script>

b. 访问子组件的内部DOM

html 复制代码
<template>
    <MyButton ref="button">按钮</MyButton>
</template>
<script>
export default {
    mounted(){
        // 错误用法:不应直接操作子组件的内部DOM
        this.$refs['button'].$refs['innerButton'].method();
    }
}
</script>

子组件耦合父组件

子组件与父组件的通信应通过接收props和抛出事件,避免直接修改父组件的数据或调用父组件的方法。错误示例:

a. 修改props

html 复制代码
<script>
export default {
    props: ['data'],
    methods:{
        edit(){
            // 错误:不应修改父组件传来的props
            this.data.type = 'vip';
        }
    }
}
</script>

b. 调用父组件方法或修改父组件数据

html 复制代码
<script>
export default {
    methods:{
        edit(){
            // 错误:不应直接修改父组件的数据或调用其方法
            this.$parent.data.type = 'vip';
            this.$parent.method();
        }
    }
}
</script>

组件耦合外部数据

组件应避免直接依赖外部数据(如全局变量、URL参数等),而应通过props传递必要的数据。错误示例:

html 复制代码
// 用户详情组件,耦合了URL参数
<script>
export default {
    data(){
        return {
            userId: this.$route.params.userId   // 耦合了URL参数
        }
    }
}
</script>

组件耦合过多业务逻辑

组件应避免耦合具体业务逻辑,通过props控制功能。错误示例:

html 复制代码
// MemberList组件
<template>
    <div>
        <div class="operate">
            <button v-if="type==='ordinary'">添加用户</button>
            <button v-if="type==='vip'">添加VIP</button>
            <button v-if="type==='super-vip'">添加超级VIP</button>
        </div>
        <MyTable>
            <template slot="operate" slot-scope="props">
                <button v-if="type!=='ordinary'">删除</button>
            </template>
        </MyTable>
    </div>
</template>
<script>
export default {
    props: ['type'],
}
</script>
相关推荐
前端爆冲8 分钟前
项目中无用export的检测方案
前端
热爱编程的小曾36 分钟前
sqli-labs靶场 less 8
前端·数据库·less
gongzemin1 小时前
React 和 Vue3 在事件传递的区别
前端·vue.js·react.js
Apifox1 小时前
如何在 Apifox 中通过 Runner 运行包含云端数据库连接配置的测试场景
前端·后端·ci/cd
树上有只程序猿1 小时前
后端思维之高并发处理方案
前端
庸俗今天不摸鱼2 小时前
【万字总结】前端全方位性能优化指南(十)——自适应优化系统、遗传算法调参、Service Worker智能降级方案
前端·性能优化·webassembly
黄毛火烧雪下2 小时前
React Context API 用于在组件树中共享全局状态
前端·javascript·react.js
Apifox2 小时前
如何在 Apifox 中通过 CLI 运行包含云端数据库连接配置的测试场景
前端·后端·程序员
一张假钞2 小时前
Firefox默认在新标签页打开收藏栏链接
前端·firefox
高达可以过山车不行2 小时前
Firefox账号同步书签不一致(火狐浏览器书签同步不一致)
前端·firefox