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

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

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

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>
相关推荐
前端Hardy16 分钟前
HTML&CSS 打造的酷炫菜单选项卡
前端·javascript·css·html·css3
M木21 分钟前
前端如何实现文件的在线预览?
前端·vue.js
阿征学IT23 分钟前
vue计算属性 初步使用案例
前端·javascript·vue.js
Qhumaing24 分钟前
html文本元素
前端·html
林太白25 分钟前
❤React-React 组件通讯
前端·javascript·react.js
码手Lion42 分钟前
CSS多列布局:打破传统布局的束缚
前端·css
青龙摄影1 小时前
【自动化】学习观看视频内容,无需人为干预
前端·vue.js·elementui
豆华1 小时前
React 中 为什么多个 JSX 标签需要被一个父元素包裹?
前端·react.js·前端框架
起风的秋天@1 小时前
CSS Modules在框架中的使用
前端·css
前端熊猫1 小时前
React第一个项目
前端·javascript·react.js