Vue 2 - Đạo cụ đột biến vue-warn


180

Tôi đã bắt đầu https://laracasts.com/series/learning-vue-step-by-step loạt. Tôi đã dừng bài học Vue, Laravel và AJAX với lỗi này:

vue.js: 2574 [Vue cảnh báo]: Tránh trực tiếp thay đổi prop vì giá trị sẽ bị ghi đè bất cứ khi nào thành phần cha mẹ tái xuất hiện. Thay vào đó, sử dụng dữ liệu hoặc thuộc tính được tính dựa trên giá trị của prop. Prop bị đột biến: "list" (tìm thấy trong thành phần)

Tôi có mã này trong main.js

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

Tôi biết rằng vấn đề nằm ở chỗ được tạo () khi tôi ghi đè lên danh sách prop, nhưng tôi là người mới ở Vue, vì vậy tôi hoàn toàn không biết cách khắc phục. Bất cứ ai cũng có một ý tưởng làm thế nào (và xin vui lòng giải thích tại sao) để sửa chữa nó?


2
Đoán, nó chỉ là một thông điệp cảnh báo và không phải là một lỗi.
David R

Câu trả lời:


264

Điều này có liên quan đến thực tế là việc thay đổi một chỗ dựa cục bộ được coi là một mô hình chống trong Vue 2

Những gì bạn nên làm bây giờ, trong trường hợp bạn muốn thay đổi prop cục bộ , là khai báo một trường trong datađó sử dụng propsgiá trị làm giá trị ban đầu và sau đó thay đổi bản sao:

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

Bạn có thể đọc thêm về điều này trên hướng dẫn chính thức của Vue.js


Lưu ý 1: Xin lưu ý rằng bạn không nên sử dụng cùng tên cho propdata , tức là:

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

Lưu ý 2:tôi cảm thấy có một số nhầm lẫn về phản ứngpropsphản ứng , tôi khuyên bạn nên xem qua chủ đề này


3
Đây là một câu trả lời tuyệt vời, nhưng tôi cũng nghĩ rằng nó nên được cập nhật để bao gồm ràng buộc sự kiện. Một sự kiện được kích hoạt từ đứa trẻ có thể cập nhật cha mẹ, nó sẽ truyền đạo cụ cập nhật cho đứa trẻ. Bằng cách đó, nguồn sự thật vẫn được duy trì trên tất cả các thành phần. Cũng có thể đáng nói đến Vuex để quản lý trạng thái được chia sẻ trên nhiều thành phần.
Wes Harper

@WesHarper, cũng có thể sử dụng công cụ sửa đổi .sync làm tốc ký để tự động nhận sự kiện cập nhật của cha mẹ.
danb4r

48

Các mẫu Vue là propsxuống và eventslên. Nghe có vẻ đơn giản, nhưng rất dễ quên khi viết một thành phần tùy chỉnh.

Kể từ Vue 2.2.0, bạn có thể sử dụng mô hình v (với các thuộc tính được tính toán ). Tôi đã tìm thấy sự kết hợp này tạo ra một giao diện đơn giản, rõ ràng và nhất quán giữa các thành phần:

  • Bất kỳ propsthông qua nào cho thành phần của bạn vẫn phản ứng (nghĩa là nó không được nhân bản cũng như không yêu cầu watchchức năng cập nhật bản sao cục bộ khi phát hiện thay đổi).
  • Thay đổi được tự động phát ra cho phụ huynh.
  • Có thể được sử dụng với nhiều cấp độ của các thành phần.

Một thuộc tính được tính cho phép setter và getter được xác định riêng. Điều này cho phép Taskthành phần được viết lại như sau:

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)
            }
        }
    }
})  

Các mô hình định nghĩa tài sản đó propgắn liền với v-model, và trong đó sự kiện sẽ được phát ra trên những thay đổi. Sau đó, bạn có thể gọi thành phần này từ cha mẹ như sau:

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

Thuộc listLocaltính được cung cấp một giao diện getter và setter đơn giản trong thành phần (nghĩ về nó giống như một biến riêng tư). Trong #task-templatebạn có thể kết xuất listLocalvà nó sẽ vẫn phản ứng (nghĩa là, nếu parentListthay đổi, nó sẽ cập nhật Taskthành phần). Bạn cũng có thể thay đổi listLocalbằng cách gọi setter (ví dụ this.listLocal = newList:) và nó sẽ phát ra thay đổi cho cha mẹ.

Điều tuyệt vời về mẫu này là bạn có thể chuyển listLocalsang một thành phần con của Task(sử dụng v-model) và các thay đổi từ thành phần con sẽ lan truyền đến thành phần cấp cao nhất.

Ví dụ: giả sử chúng ta có một EditTaskthành phần riêng để thực hiện một số loại sửa đổi đối với dữ liệu tác vụ. Bằng cách sử dụng v-modelmẫu thuộc tính giống nhau và được tính toán, chúng ta có thể chuyển listLocalđến thành phần (bằng cách sử dụng v-model):

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

Nếu EditTaskphát ra một sự thay đổi nó một cách thích hợp sẽ gọi set()trên listLocalvà qua đó tuyên truyền các sự kiện đến cấp cao nhất. Tương tự, EditTaskthành phần này cũng có thể gọi các thành phần con khác (như các phần tử biểu mẫu) bằng cách sử dụng v-model.


1
Tôi đang làm một cái gì đó tương tự ngoại trừ với đồng bộ hóa. Vấn đề là tôi cần chạy một phương thức khác sau khi phát ra sự kiện thay đổi của mình, tuy nhiên khi phương thức đó chạy và chạm vào getter, tôi nhận được giá trị cũ vì sự kiện thay đổi chưa được người nghe / truyền lại cho đứa trẻ. Đây là trường hợp tôi muốn thay đổi prop để dữ liệu cục bộ của tôi chính xác cho đến khi cập nhật cha mẹ truyền xuống. Bất kỳ suy nghĩ về vấn đề này?
koga73

Tôi nghi ngờ việc gọi getter từ setter không phải là cách thực hành tốt. Những gì bạn có thể xem xét là đặt một chiếc đồng hồ trên tài sản tính toán của bạn và thêm logic của bạn ở đó.
chris

đạo cụ xuống và sự kiện lên là những gì nhấp với tôi. Điều này được giải thích ở đâu trong tài liệu của VueJs?
nebiousGirl


Tôi nghĩ rằng đây là cách dễ dàng hơn để phát ra các thay đổi đối với thành phần cha mẹ.
Muhammad

36

Vue chỉ cảnh báo bạn: bạn thay đổi prop trong thành phần, nhưng khi kết xuất lại thành phần cha mẹ, "danh sách" sẽ bị ghi đè và bạn mất tất cả các thay đổi. Vì vậy, thật nguy hiểm khi làm như vậy.

Sử dụng tài sản tính toán thay vì như thế này:

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

25
Điều gì về một prop ràng buộc 2 cách?
Josh R.

7
Nếu bạn muốn liên kết dữ liệu 2 chiều, bạn nên sử dụng các sự kiện tùy chỉnh, để biết thêm thông tin, hãy đọc: vuejs.org/v2/guide/components.html#sync-Modifier Bạn vẫn không thể sửa đổi trực tiếp prop, bạn cần một sự kiện và chức năng xử lý một sự thay đổi chống đỡ cho bạn.
Marco

22

Nếu bạn đang sử dụng Lodash, bạn có thể sao chép prop trước khi trả lại. Mẫu này rất hữu ích nếu bạn sửa đổi chỗ dựa đó cho cả cha mẹ và con cái.

Giả sử chúng ta có danh sách chống đỡ trên lưới thành phần .

Thành phần phụ huynh

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

Trong thành phần trẻ em

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

19

Đạo cụ xuống, sự kiện lên. Đó là mẫu của Vue. Vấn đề là nếu bạn cố gắng thay đổi đạo cụ truyền từ cha mẹ. Nó sẽ không hoạt động và nó chỉ bị ghi đè nhiều lần bởi thành phần cha mẹ. Thành phần con chỉ có thể phát ra một sự kiện để thông báo cho thành phần cha mẹ thực hiện sth. Nếu bạn không thích những hạn chế này, bạn có thể sử dụng VUEX (thực tế mẫu này sẽ hút cấu trúc các thành phần phức tạp, bạn nên sử dụng VUEX!)


2
Ngoài ra còn có một tùy chọn để sử dụng xe buýt sự kiện
Steven Pribilinskiy

xe buýt sự kiện không được hỗ trợ nguyên bản trong vue 3. github.com/vuejs/rfcs/pull/118
AlexMA

15

Bạn không nên thay đổi giá trị của đạo cụ trong thành phần con. Nếu bạn thực sự cần thay đổi nó, bạn có thể sử dụng .sync. Chỉ như thế này

<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'
})

7

Theo VueJs 2.0, bạn không nên thay đổi một chỗ dựa bên trong thành phần. Họ chỉ bị đột biến bởi cha mẹ của họ. Do đó, bạn nên xác định các biến trong dữ liệu với các tên khác nhau và giữ cho chúng được cập nhật bằng cách xem các đạo cụ thực tế. Trong trường hợp prop danh sách được thay đổi bởi cha mẹ, bạn có thể phân tích cú pháp và gán nó cho mutableList. Đây là một giải pháp hoàn chỉnh.

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);
        }
    }
});

Nó sử dụng mutableList để hiển thị mẫu của bạn, do đó bạn giữ cho danh sách của bạn an toàn trong thành phần.


không phải là đồng hồ đắt tiền để được sử dụng?
Kick Buttowski

6

không thay đổi đạo cụ trực tiếp trong các thành phần. Nếu bạn cần thay đổi, hãy đặt thuộc tính mới như thế này:

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

Và thay đổi giá trị của listClone.


6

Câu trả lời rất đơn giản, bạn nên phá vỡ đột biến prop trực tiếp bằng cách gán giá trị cho một số biến thành phần cục bộ (có thể là thuộc tính dữ liệu, được tính bằng getters, setters hoặc watcher).

Đây là một giải pháp đơn giản bằng cách sử dụng trình theo dõi.

<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>

Đó là những gì tôi sử dụng để tạo bất kỳ thành phần đầu vào dữ liệu nào và nó hoạt động tốt. Bất kỳ dữ liệu mới nào được gửi (v-model (ed)) từ cha mẹ sẽ được theo dõi bởi người theo dõi giá trị và được gán cho biến đầu vào và sau khi nhận được đầu vào, chúng ta có thể bắt hành động đó và phát ra đầu vào cho cha mẹ cho rằng dữ liệu đó là đầu vào từ các yếu tố hình thức.


5

Tôi đã đối mặt với vấn đề này là tốt. Các cảnh báo đã biến mất sau khi tôi sử dụng $on$emit. Đó là một cái gì đó giống như sử dụng $onvà được $emitđề nghị gửi dữ liệu từ thành phần con đến thành phần cha mẹ.


4

Nếu bạn muốn thay đổi đạo cụ - sử dụng đối tượng.

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

thành phần:

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

Cũng như một lưu ý, bạn phải cẩn thận khi đột biến các đối tượng. Các đối tượng Javascript được truyền bằng tham chiếu, nhưng có một cảnh báo: Tham chiếu bị hỏng khi bạn đặt một biến bằng một giá trị.
ira

3

Bạn cần thêm phương thức tính toán như thế này

thành phần.vue

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

3

Luồng dữ liệu một chiều, theo https://vuejs.org/v2/guide/components.html , thành phần tuân theo Luồng dữ liệu một chiều, Tất cả các đạo cụ tạo thành một ràng buộc một chiều giữa thuộc tính con và cha mẹ một, khi thuộc tính cha mẹ cập nhật, nó sẽ chảy xuống con nhưng không theo cách khác, điều này ngăn các thành phần con vô tình làm đột biến cha mẹ, điều này có thể khiến luồng dữ liệu của ứng dụng của bạn khó hiểu hơn.

Ngoài ra, mỗi khi thành phần cha được cập nhật, tất cả các đạo cụ trong các thành phần con sẽ được làm mới với giá trị mới nhất. Điều này có nghĩa là bạn không nên cố gắng thay đổi một chỗ dựa bên trong một thành phần con. Nếu bạn làm .vue sẽ cảnh báo bạn trong bảng điều khiển.

Thường có hai trường hợp mà nó muốn thay đổi một prop: prop được sử dụng để vượt qua trong một giá trị ban đầu; thành phần con muốn sử dụng nó như một thuộc tính dữ liệu cục bộ sau đó. Prop được truyền vào như một giá trị thô cần được chuyển đổi. Câu trả lời thích hợp cho các trường hợp sử dụng này là: Xác định thuộc tính dữ liệu cục bộ sử dụng giá trị ban đầu của prop làm giá trị ban đầu:

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

Xác định một thuộc tính được tính toán từ giá trị của prop:

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

2

Các đạo cụ của Vue.js không bị thay đổi vì đây được coi là một Anti-Pattern trong Vue.

Cách tiếp cận bạn cần thực hiện là tạo một thuộc tính dữ liệu trên thành phần của bạn tham chiếu thuộc tính prop gốc của danh sách

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

Bây giờ cấu trúc danh sách của bạn được truyền cho thành phần được tham chiếu và biến đổi thông qua thuộc datatính của thành phần của bạn :-)

Nếu bạn muốn làm nhiều hơn là chỉ phân tích thuộc tính danh sách của mình thì hãy sử dụng thuộc tính của thành phần Vue computed. Điều này cho phép bạn tạo ra nhiều đột biến sâu hơn cho đạo cụ của mình.

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

Ví dụ trên phân tích cú pháp danh sách của bạn và lọc nó xuống chỉ thành các danh sách đang hoạt động , đăng xuất nó ra cho schnitts và cười khúc khích và trả về nó.

lưu ý : cả hai thuộc tính data& computedđược tham chiếu trong mẫu giống nhau, vd

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

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

Có thể dễ dàng nghĩ rằng một computedtài sản (là một phương thức) cần được gọi là ... nó không


2
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'
})

Có lẽ điều này sẽ đáp ứng nhu cầu của bạn.


2

Thêm vào câu trả lời hay nhất,

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

Đặt đạo cụ theo một mảng có nghĩa là cho dev / tạo mẫu, trong sản xuất, đảm bảo đặt các loại prop ( https://vuejs.org/v2/guide/components-props.html ) và đặt giá trị mặc định trong trường hợp prop không được cha mẹ cư trú, như vậy.

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

Bằng cách này, bạn ít nhất nhận được một đối tượng trống mutableListthay vì lỗi JSON.parse nếu nó không được xác định.


0

Vue.js coi đây là một mô hình chống. Ví dụ: khai báo và thiết lập một số đạo cụ như

this.propsVal = 'new Props Value'

Vì vậy, để giải quyết vấn đề này, bạn phải lấy một giá trị từ các đạo cụ đến dữ liệu hoặc thuộc tính được tính toán của một thể hiện Vue, như sau:

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

Điều này chắc chắn sẽ làm việc.


0

Ngoài những điều trên, đối với những người khác có vấn đề sau:

"Nếu giá trị đạo cụ là không bắt buộc và do đó không phải lúc nào cũng được trả về, dữ liệu đã truyền sẽ trả về undefined(thay vì trống)". Mà có thể gây rối <select>giá trị mặc định, tôi đã giải quyết nó bằng cách kiểm tra xem giá trị đó có được đặt trong beforeMount()(và đặt nó nếu không) như sau:

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>

0

Tôi muốn đưa ra câu trả lời này giúp tránh sử dụng nhiều mã, trình theo dõi và các thuộc tính được tính toán. Trong một số trường hợp, đây có thể là một giải pháp tốt:

Đạo cụ được thiết kế để cung cấp thông tin liên lạc một chiều.

Khi bạn có một show/hidenút phương thức với một chỗ dựa, giải pháp tốt nhất với tôi là phát ra một sự kiện:

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

Sau đó thêm người nghe vào phần tử phương thức:

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

(Trong trường hợp này, prop showcó lẽ không cần thiết vì bạn có thể sử dụng dễ dàng v-if="show"trực tiếp trên phương thức cơ sở)


0

Cá nhân tôi luôn đề nghị nếu bạn cần thay đổi đạo cụ, trước tiên hãy chuyển chúng sang tài sản được tính toán và trở về từ đó, sau đó người ta có thể biến đổi đạo cụ một cách dễ dàng, ngay cả khi bạn có thể theo dõi đột biến prop, nếu chúng bị đột biến từ người khác thành phần quá hoặc chúng tôi cũng có thể xem.


0

Bởi vì đạo cụ Vue là luồng dữ liệu một chiều, Điều này ngăn các thành phần con vô tình làm thay đổi trạng thái của cha mẹ.

Từ tài liệu chính thức của Vue, chúng tôi sẽ tìm ra 2 cách để giải quyết vấn đề này

  1. nếu thành phần con muốn sử dụng đạo cụ làm dữ liệu cục bộ, tốt nhất là xác định thuộc tính dữ liệu cục bộ.

      props: ['list'],
      data: function() {
        return {
          localList: JSON.parse(this.list);
        }
      }
    
  2. Prop được truyền vào như một giá trị thô cần được chuyển đổi. Trong trường hợp này, tốt nhất là xác định thuộc tính được tính bằng giá trị của 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)
        }
        //...whatever to transform the list
      }
    
    

0

CÓ!, Thuộc tính đột biến trong vue2 là chống mẫu. NHƯNG ... Chỉ cần phá vỡ các quy tắc bằng cách sử dụng các quy tắc khác, và đi về phía trước! Những gì bạn cần là thêm công cụ sửa đổi .sync vào thuộc tính thành phần của bạn trong phạm vi paret. <your-awesome-components :custom-attribute-as-prob.sync="value" />

Simple Thật đơn giản, chúng ta giết người dơi


0

Vì khi TypeScript là lang ưa thích của bạn. của sự phát triển

<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;
}

và không

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



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

0

Dưới đây là một thành phần quán bar, khi tôi cung cấp cho các snackbar biến trực tiếp vào v-mô hình như thế này nếu sẽ làm việc nhưng trong giao diện điều khiển, nó sẽ cho một lỗi như

Tránh đột biến một prop trực tiếp vì giá trị sẽ được ghi đè bất cứ khi nào thành phần cha mẹ tái xuất hiện. Thay vào đó, sử dụng dữ liệu hoặc thuộc tính được tính dựa trên giá trị của 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>

Cách chính xác để thoát khỏi lỗi đột biến này là sử dụng trình theo dõi

<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>

Vì vậy, trong thành phần chính nơi bạn sẽ tải thanh đồ ăn nhẹ này, bạn chỉ có thể thực hiện mã này

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

Điều này làm việc cho tôi

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.