【Vue.JS】Render 实现留言板实例及 Avoid mutating a prop directly 错误处理

版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/shaotaiban1097/article/details/91463032

声明:文中代码整体思路来源于 梁灏 编著的 【Vue.JS 实战】一书,学习过程中发现一处问题。以做记录

效果图

img

代码

  • index.html
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>BBS</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" type="text/css" media="screen" href="main.css" />
</head>

<body>
    <div id="demo" v-cloak style="width:500px;margin:0 auto">
        <div class="message">
            <v-input v-model="username"></v-input>
            <v-textarea v-model="message" ref="message"></v-textarea>
            <button @click="handleSend">发送</button>
        </div>
        <list :list="list" @reply="handleReply"></list>
    </div>
    <script src="https://cdn.bootcss.com/vue/2.6.10/vue.js"></script>
    <script src="input.js"></script>
    <script src="list.js"></script>
    <script src="main.js"></script>
</body>

</html>
  • main.js
var demo = new Vue({
    el: '#demo',
    data: {
        username: '',
        message: '',
        list: []
    },
    methods: {
        handleSend() {
            if (this.username === '' || this.message === '') {
                alert('不能为空');
                return;
            }
            this.list.push({
                username: this.username,
                message: this.message,
            });
            this.message = '';
        },
        handleReply(index) {
            var name = this.list[index].username;
            this.message = "回复@" + name + ':';
            this.$refs.message.focus();
        }
    },
})
  • input.js
Vue.component('vInput', {

    props: {
        value: {
            type: [String, Number],
            default: ''
        }
    },

    render: function (createElement) {
        var _this = this;
        return createElement('div', [
            createElement('span', '昵称:'),
            createElement('input', {
                attrs: {
                    type: 'text'
                },
                domProps: {
                    value: this.value,
                },
                on: {
                    input: function (event) {
                        _this.value = event.target.value;
                        _this.$emit('input', event.target.value);
                    }
                }
            })
        ]);
    }
});

Vue.component('vTextarea', {
    props: {
        value: {
            type: [String, Object],
            default: '',
        }
    },
    render: function (createElement) {
        var _this = this;
        return createElement('div', [
            createElement('span', '留言内容:'),
            createElement('textarea', {
                attrs: {
                    placeholder: '请输入内容',
                },
                domProps: {
                    value: this.value,
                },
                ref: 'message',
                on: {
                    input: function (event) {
                        //_this.value = event.target.value;
                        _this.$emit('input', event.target.value);
                    }
                }
            })
        ])
    },
    methods: {
        focus: function () {
            this.$refs.message.focus();
        }
    },
})
  • list.js
Vue.component('list', {
    props: {
        list: {
            type: [Array],
            default: function () {
                return [];
            }
        }
    },

    render: function (createElement) {
        var _this = this;
        var list = [];
        this.list.forEach((item, index) => {
            var node = createElement('div', {
                attrs: {
                    class: 'list-item',
                }
            }, [
                createElement('span', item.username + ':'),
                createElement('div', {
                    attrs: {
                        class: 'list-msg',
                    }
                }, [
                    createElement('p', item.message),
                    createElement('a', {
                        attrs: {
                            class: 'list-reply'
                        },
                        on: {
                            click: function () {
                                _this.handleReply(index);
                            }
                        },
                    }, '回复'),
                ])
            ]);
            list.push(node);
        });
        if (this.list.length) {
            return createElement('div', {
                attrs: {
                    class: 'list',
                },
            }, list);
        } else {
            return createElement('div', {
                attrs: {
                    class: 'list-nothing',
                }
            }, '列表为空')
        }
    },

    methods: {
        handleReply: function (index) {
            this.$emit('reply', index);
        }
    },
})
  • main.css
[v-cloak] {
    display: none;
}

* {
    padding: 0;
    margin: 0;
}

.message {
    width: 450px;
    text-align: right;
}

.message div {
    margin-bottom: 12px;
}

.message span {
    display: inline-block;
    width: 100px;
    vertical-align: top;
}

.message input,
.message textarea {
    width: 300px;
    height: 32px;
    padding: 0 6px;
    color: #657180;
    border: 1px solid #d7dde4;
    border-radius: 4px;
    cursor: text;
    outline: none;
}

.message input:focus,
.message textarea:focus {
    border: 1px solid #3399FF;
}

.message textarea {
    height: 60px;
    padding: 4px 6px;
}

.message button {
    display: inline-block;
    padding: 6px 15px;
    border: 1px solid #39F;
    border-radius: 4px;
    color: #FFF;
    background-color: #39F;
    cursor: pointer;
    outline: none;
}

.list {
    margin-top: 50px;
}

.list-item {
    padding: 10px;
    border-bottom: 1px solid #e3e8ee;
    overflow: hidden;
}

.list-item span {
    display: block;
    width: 120px;
    float: left;
    color: #39F;
}

.list-msg {
    display: block;
    margin-left: 60px;
    text-align: justify;
}

.list-msg a {
    color: #9ea7b4;
    cursor: pointer;
    float: right;
}

.list-msg a:hover {
    color: #39F;
}

.list-nothing {
    text-align: center;
    color: #9ea7b4;
    padding: 20px;
}

遇到问题

在测试过程中,没有发现功能问题,但是,点开 F12 后
error

虽然标记的是 Vue warn ,仅仅是警告,而且也没有影响到功能的使用,但显示成红色的 Error 总归有些不爽的。
于是,开始查找原因 …
发现是 由于 input.js 中监听 input 事件时 _this.value = event.target.value; 这一行代码引起的错误。

查阅本书之前对组件传值的描述,并结合Vue的官方文档,得出问题原因如下

  • 错误描述
    Vue Warning:
    Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders.
    Instead, use a data or computed property based on the prop’s value. Prop being mutated: “value”
  • 中文对照
    避免直接修改属性,因为只要父组件重新渲染,该值就会被覆盖,应该根据 prop 的值使用 data 或计算属性
  • 本书资料
    Vue 1.X 中提供 .sync 修饰符支持双向绑定,Vue 2.X 中只支持 props 单向传递数据,目的是尽可能的将父子组件解耦,避免子组件无意中修改父组件内容。
  • 官方资料
    所有的prop都使其父子之间形成单向下行绑定:即父级prop的更新会流动到子级,但是反向不允许,为了防止子级意外改变父级组件状态,每次父组件发生更新时,子组件中的所有prop都会刷新为最新值。这意味着操作者不应该在子组件中修改prop的值。如果这样做,Vue将会在浏览器中发出警告。

也就是说,报错的根本原因是 _this.value = event.target.value; 这一句话在子组件中对父组件的 value 进行了赋值操作,这在 Vue 2.X 中是不被允许的。

修改方法

首先要分析是否的确需要在子组件中更新父组件的值。

  • 确认需要在子组件中操作父组件的值
    • 父组件直接调用子组件方法,子组件通过参数传值
    • 子组件使用 $emit() 方法触发事件,将值当做参数传递,父组件使用 on 方法监听事件。(示例中使用该方法)
    • 示例:
    render: function (createElement) {
    	......
    		on: {
                input: function (event) {
                	_this.$emit('input', event.target.value);
                }
           }
    }
    
  • 仅在子组件中使用且操作该值
    • 使用 data 在子组件中存储该值,所有对该值的操作尽在子组件中有效,不影响父组件
    • 使用 computed 计算属性存储该值,效果与使用 data 类似。
    • 示例:
    Vue.component('vInput', {
    props: {
        value: {
            type: [String, Number],
            default: ''
        }
    },
    data(){
        return{
            text : this.value,
        }
    },
    render: function (createElement) {
    	......
    		on: {
                input: function (event) {
                	_this.text = event.target.value;
                }
           }
    }
    ....       
    

猜你喜欢

转载自blog.csdn.net/shaotaiban1097/article/details/91463032