ChatGPT解决这个技术问题 Extra ChatGPT

Vue 2 - 变异道具 vue-warn

我开始了 https://laracasts.com/series/learning-vue-step-by-step 系列。我在 Vue、Laravel 和 AJAX 课程上停了下来,但出现了以下错误:

vue.js:2574 [Vue 警告]:避免直接改变 prop,因为只要父组件重新渲染,该值就会被覆盖。相反,使用基于道具值的数据或计算属性。正在变异的道具:“列表”(在组件中找到)

我在 main.js 中有这段代码

Vue.component('task', {
    template: '#task-template',
    props: ['list'],
    created() {
        this.list = JSON.parse(this.list);
    }
});
new Vue({
    el: '.container'
})

当我覆盖列表道具时,我知道问题出在 created() 中,但我是 Vue 的新手,所以我完全不知道如何解决它。有谁知道如何(并请解释原因)修复它?

猜猜,这只是一条警告消息,而不是错误。
您需要使用一个变量创建一个数据结构,该变量将保存 JSON.parse 结果的结果:date() { return {val: null}} 并且在创建的部分中,您应该分配值 created() {this.val = JSON.parse(this.list)}

t
tony19

这与 mutating a prop locally is considered an anti-pattern in Vue 2

如果您想在本地修改 props,您现在应该做的是在您的 data 中声明一个使用 props 值作为其初始值的字段,然后修改副本:

Vue.component('task', {
    template: '#task-template',
    props: ['list'],
    data: function () {
        return {
            mutableList: JSON.parse(this.list);
        }
    }
});

您可以在 Vue.js official guide 上阅读有关此内容的更多信息

注意 1: 请注意 you should not use the same name for your prop and data,即:

data: function () { return { list: JSON.parse(this.list) } } // WRONG!!

注意 2: 由于 I feel there is some confusion 关于 propsreactivity,我建议您查看 this 线程


这是一个很好的答案,但我也认为应该更新它以包含事件绑定。从孩子触发的事件可以更新父母,这会将更新的道具传递给孩子。这样一来,所有组件的真实来源仍然得到维护。还值得一提的是 Vuex 用于管理跨多个组件共享的状态。
@WesHarper,也可以使用 .sync modifier 作为速记来自动获取父级的更新事件。
t
tony19

Vue 模式是 props 向下和 events 向上。这听起来很简单,但在编写自定义组件时很容易忘记。

从 Vue 2.2.0 开始,您可以使用 v-model(和 computed properties)。我发现这种组合在组件之间创建了一个简单、干净且一致的接口:

传递给您的组件的任何道具都保持反应性(即,它不会被克隆,也不需要监视功能来在检测到更改时更新本地副本)。

更改会自动发送给父级。

可以与多个级别的组件一起使用。

计算属性允许单独定义 setter 和 getter。这允许按如下方式重写 Task 组件:

Vue.component('Task', {
    template: '#task-template',
    props: ['list'],
    model: {
        prop: 'list',
        event: 'listchange'
    },
    computed: {
        listLocal: {
            get: function() {
                return this.list
            },
            set: function(value) {
                this.$emit('listchange', value)
            }
        }
    }
})  

model 属性定义哪个 propv-model 相关联,以及在更改时将发出哪个事件。然后,您可以从父级调用此组件,如下所示:

<Task v-model="parentList"></Task>

listLocal 计算属性在组件内提供了一个简单的 getter 和 setter 接口(将其视为私有变量)。在 #task-template 中,您可以呈现 listLocal,它会保持反应状态(即,如果 parentList 发生更改,它将更新 Task 组件)。您还可以通过调用 setter(例如 this.listLocal = newList)来改变 listLocal,它会将更改发送给父级。

这种模式的优点在于您可以将 listLocal 传递给 Task 的子组件(使用 v-model),并且子组件的更改将传播到顶级组件。

例如,假设我们有一个单独的 EditTask 组件,用于对任务数据进行某种类型的修改。通过使用相同的 v-model 和计算属性模式,我们可以将 listLocal 传递给组件(使用 v-model):

<script type="text/x-template" id="task-template">
    <div>
        <EditTask v-model="listLocal"></EditTask>
    </div>
</script>

如果 EditTask 发出更改,它将适当地调用 listLocal 上的 set(),从而将事件传播到顶层。同样,EditTask 组件也可以使用 v-model 调用其他子组件(例如表单元素)。


除了同步之外,我正在做类似的事情。问题是我需要在发出更改事件后运行另一个方法,但是当方法运行并命中 getter 时,我得到旧值,因为更改事件尚未被侦听器拾取/传播回子级。在这种情况下,我想改变道具,以便我的本地数据是正确的,直到父更新传播回来。在这方面有什么想法吗?
我怀疑从 setter 调用 getter 不是一个好习惯。您可能会考虑对您的计算属性进行监视并在那里添加您的逻辑。
道具下降和事件上升是我的想法。这在 VueJs 的文档中在哪里解释?
我认为这是向父组件发出更改的更轻松的方式。
c
claudios

Vue 只是警告您:您更改了组件中的 prop,但是当父组件重新渲染时,“list”将被覆盖,您将丢失所有更改。所以这样做很危险。

像这样使用计算属性:

Vue.component('task', {
    template: '#task-template',
    props: ['list'],
    computed: {
        listJson: function(){
            return JSON.parse(this.list);
        }
    }
});

2路绑定道具怎么样?
如果您想要 2 路数据绑定,您应该使用自定义事件,有关详细信息,请阅读:vuejs.org/v2/guide/components.html#sync-Modifier 您仍然无法直接修改道具,您需要一个事件和函数来为您处理道具更改。
U
Ulysse BN

如果你使用 Lodash,你可以在返回之前克隆 prop。如果您在父子节点上修改该道具,此模式会很有帮助。

假设我们在组件网格上有道具列表。

在父组件中

<grid :list.sync="list"></grid>

在子组件中

props: ['list'],
methods:{
    doSomethingOnClick(entry){
        let modifiedList = _.clone(this.list)
        modifiedList = _.uniq(modifiedList) // Removes duplicates
        this.$emit('update:list', modifiedList)
    }
}

D
Diwas Poudel

道具下降,事件上升。这就是 Vue 的模式。关键是,如果您尝试改变从父级传递的道具。它不起作用,它只会被父组件反复覆盖。子组件只能发出事件通知父组件做某事。如果你不喜欢这些限制,你可以使用 VUEX(实际上这种模式会吸收复杂的组件结构,你应该使用 VUEX!)


还有一个使用事件总线的选项
vue 3 本身不支持事件总线。github.com/vuejs/rfcs/pull/118
A
Alexis Tyler

您不应更改子组件中道具的值。如果您确实需要更改它,您可以使用 .sync。像这样

<your-component :list.sync="list"></your-component>

Vue.component('task', {
    template: '#task-template',
    props: ['list'],
    created() {
        this.$emit('update:list', JSON.parse(this.list))
    }
});
new Vue({
    el: '.container'
})

b
burak buruk

根据 VueJs 2.0,你不应该在组件内改变一个 prop。他们只是由他们的父母变异。因此,您应该在 data 中定义不同名称的变量,并通过查看实际 props 来保持更新。如果列表道具被父级更改,您可以对其进行解析并将其分配给 mutableList。这是一个完整的解决方案。

Vue.component('task', {
    template: ´<ul>
                  <li v-for="item in mutableList">
                      {{item.name}}
                  </li>
              </ul>´,
    props: ['list'],
    data: function () {
        return {
            mutableList = JSON.parse(this.list);
        }
    },
    watch:{
        list: function(){
            this.mutableList = JSON.parse(this.list);
        }
    }
});

它使用 mutableList 来呈现您的模板,因此您可以在组件中保持列表道具的安全。


手表用起来不贵吗?
V
Vectrobyte

答案很简单,您应该通过将值分配给一些局部组件变量(可以是数据属性,使用 getter、setter 或 watchers 计算)来打破直接的 prop 突变。

这是一个使用观察者的简单解决方案。

<template>
  <input
    v-model="input"
    @input="updateInput" 
    @change="updateInput"
  />

</template>

<script>
  export default {
  props: {
    value: {
      type: String,
      default: '',
    },
  },
  data() {
    return {
      input: '',
    };
  },
  watch: {
    value: {
      handler(after) {
        this.input = after;
      },
      immediate: true,
    },
  },
  methods: {
    updateInput() {
      this.$emit('input', this.input);
    },
  },
};
</script>

我用它来创建任何数据输入组件,它工作得很好。任何从父级发送的新数据(v-model(ed))都将被值观察者监视并分配给输入变量,一旦接收到输入,我们就可以捕获该动作并将输入发送给父级,表明数据已输入从表单元素。


对 data.input 使用单词“输入”恕我直言,当它与 @input 事件发生冲突时会让人感到困惑。从技术上讲,当从子视角内部看时,它将被输出。当父母订阅它时,它是来自孩子的“输入”。最好将其称为 data.name 或 smth clear,不依赖于视角,也不会将名称与事件冲突
@DimitryK 这是 v-model 的问题。显然,人们已经失去了理智,并积极提倡在任何组件上使用 v-model,无论该组件是否与用户输入有任何关系。 Vuetify 这样做会对他们的 v-dialogv-bottom-sheet 组件产生巨大的负面影响。在对话框或底部工作表上监听 @input 近乎疯狂,因为这两个组件都不接受任何类型的输入 - 并且将它们编码为就好像它们接受一样是一种严重的反模式。
T
Tango5614

不要直接在组件中更改道具。如果需要更改它,请设置一个新属性,如下所示:

data () {
    return () {
        listClone: this.list
    }
}

并更改 listClone 的值。


i
ihsanberahim

我也遇到过这个问题。在我使用 $on$emit 后警告消失了。类似于建议使用 $on$emit 将数据从子组件发送到父组件。


A
Aiwass 418

如果你想改变道具 - 使用 object.

<component :model="global.price"></component>

零件:

props: ['model'],
methods: {
  changeValue: function() {
    this.model.value = "new value";
  }
}

需要注意的是,在改变对象时必须小心。 Javascript 对象是通过引用传递的,但有一个警告:当您将变量设置为等于某个值时,引用会被破坏。
u
ubastosir

您需要添加这样的计算方法

组件.vue

props: ['list'],
computed: {
    listJson: function(){
        return JSON.parse(this.list);
    }
}

D
D.P
Vue.component('task', {
    template: '#task-template',
    props: ['list'],
    computed: {
      middleData() {
        return this.list
      }
    },
    watch: {
      list(newVal, oldVal) {
        console.log(newVal)
        this.newList = newVal
      }
    },
    data() {
      return {
        newList: {}
      }
    }
});
new Vue({
    el: '.container'
})

也许这将满足您的需求。


M
Mr.Web

我想给出这个答案,它有助于避免使用大量代码、观察者和计算属性。在某些情况下,这可能是一个很好的解决方案:

道具旨在提供单向通信。

当您有一个带有道具的模态 show/hide 按钮时,对我来说最好的解决方案是发出一个事件:

<button @click="$emit('close')">Close Modal</button>

然后将侦听器添加到模态元素:

<modal :show="show" @close="show = false"></modal>

(在这种情况下,可能不需要道具 show,因为您可以直接在基本模式上使用简单的 v-if="show"


t
tony19

单向数据流,根据https://v2.vuejs.org/v2/guide/components.html,组件遵循单向数据流,所有的props在子属性和父属性之间形成单向向下绑定,当父属性更新时,它会向下流对子组件而不是相反,这可以防止子组件意外地改变父组件,这会使您的应用程序的数据流更难理解。

此外,每次父组件更新时,子组件中的所有道具都会刷新为最新值。这意味着您不应该尝试改变子组件内的道具。如果你这样做,.vue 会在控制台中警告你。

通常有两种情况很容易改变一个 prop: prop 用于传入初始值;子组件之后希望将其用作本地数据属性。 prop 作为需要转换的原始值传入。这些用例的正确答案是: 定义一个使用 prop 的初始值作为其初始值的本地数据属性:

props: ['initialCounter'],
data: function () {
  return { counter: this.initialCounter }
}

定义一个根据 prop 的值计算的计算属性:

props: ['size'],
computed: {
  normalizedSize: function () {
    return this.size.trim().toLowerCase()
  }
}

F
Francis Leigh

Vue.js 的 props 不能被改变,因为这在 Vue 中被认为是一种反模式。

您需要采取的方法是在组件上创建一个引用 list 的原始 prop 属性的数据属性

props: ['list'],
data: () {
  return {
    parsedList: JSON.parse(this.list)
  }
}

现在,传递给组件的列表结构通过组件的 data 属性被引用和改变:-)

如果您希望做的不仅仅是解析列表属性,请使用 Vue 组件的 computed 属性。这使您可以对道具进行更深入的突变。

props: ['list'],
computed: {
  filteredJSONList: () => {
    let parsedList = JSON.parse(this.list)
    let filteredList = parsedList.filter(listItem => listItem.active)
    console.log(filteredList)
    return filteredList
  }
}

上面的示例解析您的列表道具并将其过滤为仅活动列表项,将其注销以查看 schnitts 和 gggles 并返回它。

注意datacomputed 属性在模板中引用相同,例如

<pre>{{parsedList}}</pre>

<pre>{{filteredJSONList}}</pre>

很容易认为需要调用 computed 属性(作为方法)......它不需要


c
cRAN

当 TypeScript 是你的首选语言时。发展的

<template>
<span class="someClassName">
      {{feesInLocale}}
</span>
</template>  



@Prop({default: 0}) fees: any;

// computed are declared with get before a function
get feesInLocale() {
    return this.fees;
}

并不是

<template>
<span class="someClassName">
      {{feesInLocale}}
</span>
</template>  



@Prop() fees: any = 0;
get feesInLocale() {
    return this.fees;
}

A
Asif Ashraf

Vue3 有一个非常好的解决方案。花了几个小时才到达那里。但效果真的很好。

在父模板上

<user-name
  v-model:first-name="firstName"
  v-model:last-name="lastName"
></user-name>

子组件

app.component('user-name', {
      props: {
      firstName: String,
      lastName: String
     },
template: `
   <input 
  type="text"
  :value="firstName"
  @input="$emit('update:firstName', 
 $event.target.value)">

<input
  type="text"
  :value="lastName"
   @input="$emit('update:lastName', 
    $event.target.value)">
  `
})

这是唯一进行双向绑定的解决方案。我喜欢前两个答案是使用 SYNC 和 Emitting 更新事件以及计算属性 getter 设置器的好方法,但那是一项工作,我不喜欢这么努力。


将事件转发给父级,而不是复制,这符合 Vue 的真正精神。这是迄今为止最好的解决方案;在孩子中制作第二份副本的公认答案应该是最后的手段。顺便说一句,这种方法适用于 Vue2 和 Vue3。
A
Adesanoye Samson

将道具分配给新变量。

data () {
    return {
        listClone: this.list
    }
}

t
tony19

添加到最佳答案,

Vue.component('task', {
    template: '#task-template',
    props: ['list'],
    data: function () {
        return {
            mutableList: JSON.parse(this.list);
        }
    }
});

通过数组设置道具用于开发/原型设计,在生产中确保设置道具类型(https://v2.vuejs.org/v2/guide/components-props.html)并设置默认值,以防道具未被父级填充,因此。

Vue.component('task', {
    template: '#task-template',
    props: {
      list: {
        type: String,
        default() {
          return '{}'
        }
      }
    },
    data: function () {
        return {
            mutableList: JSON.parse(this.list);
        }
    }
});

这样,如果未定义,您至少会在 mutableList 中获得一个空对象,而不是 JSON.parse 错误。


A
Ali

是的!,vue2 中的变异属性是反模式。但是……只要用其他规则来打破规则,然后继续前进!您需要将 .sync 修饰符添加到 paret 范围内的组件属性。 <your-awesome-components :custom-attribute-as-prob.sync="value" />

🤡我们杀了蝙蝠侠很简单😁


l
lokesh

下面是一个小吃店组件,当我像这样将小吃店变量直接输入 v-model 时,如果可以工作,但在控制台中,它会给出一个错误

避免直接改变 prop,因为只要父组件重新渲染,该值就会被覆盖。相反,使用基于道具值的数据或计算属性。

<template>
        <v-snackbar v-model="snackbar">
        {{ text }}
      </v-snackbar>
</template>

<script>
    export default {
        name: "loader",

        props: {
            snackbar: {type: Boolean, required: true},
            text: {type: String, required: false, default: ""},
        },

    }
</script>

摆脱这种突变错误的正确方法是使用观察者

<template>
        <v-snackbar v-model="snackbarData">
        {{ text }}
      </v-snackbar>
</template>

<script>
/* eslint-disable */ 
    export default {
        name: "loader",
         data: () => ({
          snackbarData:false,
        }),
        props: {
            snackbar: {type: Boolean, required: true},
            text: {type: String, required: false, default: ""},
        },
        watch: { 
        snackbar: function(newVal, oldVal) { 
          this.snackbarData=!this.snackbarDatanewVal;
        }
      }
    }
</script>

因此,在您将加载此小吃店的主要组件中,您只需执行此代码

 <loader :snackbar="snackbarFlag" :text="snackText"></loader>

这对我有用


C
Community

Vue.js 认为这是一种反模式。例如,声明和设置一些道具,如

this.propsVal = 'new Props Value'

因此,要解决这个问题,您必须从 Vue 实例的数据或计算属性的 props 中获取一个值,如下所示:

props: ['propsVal'],
data: function() {
   return {
       propVal: this.propsVal
   };
},
methods: {
...
}

这肯定会奏效。


M
Maroun Melhem

除上述内容外,对于其他有以下问题的人:

“如果不需要 props 值因此并不总是返回,则传递的数据将返回 undefined(而不是空)”。这可能会弄乱 <select> 默认值,我通过检查该值是否在 beforeMount() 中设置(如果没有设置)来解决它,如下所示:

JS:

export default {
        name: 'user_register',
        data: () => ({
            oldDobMonthMutated: this.oldDobMonth,
        }),
        props: [
            'oldDobMonth',
            'dobMonths', //Used for the select loop
        ],
        beforeMount() {
           if (!this.oldDobMonth) {
              this.oldDobMonthMutated = '';
           } else {
              this.oldDobMonthMutated = this.oldDobMonth
           }
        }
}

html:

<select v-model="oldDobMonthMutated" id="dob_months" name="dob_month">

 <option selected="selected" disabled="disabled" hidden="hidden" value="">
 Select Month
 </option>

 <option v-for="dobMonth in dobMonths"
  :key="dobMonth.dob_month_slug"
  :value="dobMonth.dob_month_slug">
  {{ dobMonth.dob_month_name }}
 </option>

</select>

m
mohit dutt

我个人总是建议如果您需要改变道具,首先将它们传递给计算属性并从那里返回,然后可以轻松地改变道具,即使您可以跟踪道具突变,如果它们从另一个突变组件也可以,或者我们也可以观看。


f
flypan

因为 Vue props 是一种数据流动的方式,这可以防止子组件意外改变父组件的状态。

从 Vue 官方文档中,我们会找到 2 种方法来解决这个问题

如果子组件想要使用 props 作为本地数据,最好定义一个本地数据属性。道具:['list'],数据:function() { return { localList: JSON.parse(this.list); prop 作为需要转换的原始值传入。在这种情况下,最好使用 prop 的值定义计算属性: props: ['list'], computed: { localList: function() { return JSON.parse(this.list); }, //eg: if you want to filter this list validList: function() { return this.list.filter(product => product.isValid === true) } //...不管怎样转换列表 }


l
laravel monk

您应该始终避免在 vue 或任何其他框架中改变 props。您可以采取的方法是将其复制到另一个变量中。

例如。 // instead of replacing the value of this.list use a different variable

this.new_data_variable = JSON.parse(this.list)


u
ubershmekel

一个潜在的解决方案是使用全局变量。

import { Vue } from "nuxt-property-decorator";

export const globalStore = new Vue({
  data: {
    list: [],
  },
}

export function setupGlobalsStore() {
  Vue.prototype.$globals = globalStore;
}

然后你会使用:

$globals.list

任何你需要对其进行变异或呈现的地方。


如果你这样做,那你为什么要使用 Vue。全局变量不应该用于局部变量。

关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅