为什么会出现 'do not mutate vuex store state outside mutation handlers' 错误?
Why 'do not mutate vuex store state outside mutation handlers' error shows up?
我在这里看到了不同的类似主题,但它们没有解决我的问题。我尝试将 Three.js 与 Vue3 (+Vuex) 结合使用。我彻底遵循了本教程:https://stagerightlabs.com/blog/vue-and-threejs-part-one(并且它在该站点上有效)但是我的相同代码不起作用,它抛出错误:
Uncaught (in promise) Error: [vuex] do not mutate vuex store state outside mutation handlers.
我在代码中的任何地方都看不到状态在突变处理程序之外发生突变。我不明白为什么会出现此错误。可能跟Vue3本身有关系?教程里的app估计是用Vue2写的,不知道会不会出问题
这是我的代码。抱歉,如果它看起来很长,但希望它有助于检测根本原因。这也是可重现的例子:https://codesandbox.io/s/black-microservice-y2jo6?file=/src/components/BaseModel.vue
BaseModel.vue
<template>
<div class="viewport"></div>
</template>
<script>
import { mapMutations, mapActions } from 'vuex'
export default {
name: 'BaseModel',
components: {
},
data() {
return {
height: 0
};
},
methods: {
...mapMutations(["RESIZE"]),
...mapActions(["INIT", "ANIMATE"])
},
mounted() {
this.INIT({
width: this.$el.offsetWidth,
width: this.$el.offsetHeight,
el: this.$el
})
.then(() => {
this.ANIMATE();
window.addEventListener("resize", () => {
this.RESIZE({
width: this.$el.offsetWidth,
height: this.$el.offsetHeight
});
}, true);
});
}
}
</script>
<style scoped>
.viewport {
height: 100%;
width: 100%;
}
</style>
store.js
import { TrackballControls } from 'three/examples/jsm/controls/TrackballControls.js'
import {
Scene,
PerspectiveCamera,
WebGLRenderer,
Color,
FogExp2,
CylinderBufferGeometry,
MeshPhongMaterial,
Mesh,
DirectionalLight,
AmbientLight,
} from 'three'
const state = {
width: 0,
height: 0,
camera: null,
controls: null,
scene: null,
renderer: null,
pyramids: []
};
const mutations = {
SET_VIEWPORT_SIZE(state, {width, height}){
state.width = width;
state.height = height;
},
INITIALIZE_RENDERER(state, el){
state.renderer = new WebGLRenderer({ antialias: true });
state.renderer.setPixelRatio(window.devicePixelRatio);
state.renderer.setSize(state.width, state.height);
el.appendChild(state.renderer.domElement);
},
INITIALIZE_CAMERA(state){
state.camera = new PerspectiveCamera(60, state.width/state.height, 1, 1000);
state.camera.position.set(0,0,500);
},
INITIALIZE_CONTROLS(state){
state.controls = new TrackballControls(state.camera, state.renderer.domElement);
state.controls.rotateSpeed = 1.0;
state.controls.zoomSpeed = 1.2;
state.controls.panSpeed = 0.8;
state.controls.noZoom = false;
state.controls.noPan = false;
state.controls.staticMoving = true;
state.controls.dynamicDampingFactor = 0.3;
},
INITIALIZE_SCENE(state){
state.scene = new Scene();
state.scene.background = new Color(0xcccccc);
state.scene.fog = new FogExp2(0xcccccc, 0.002);
var geometry = new CylinderBufferGeometry(0,10,30,4,1);
var material = new MeshPhongMaterial({
color: 0xffffff,
flatShading: true
});
for(var i = 0; i < 500; i++){
var mesh = new Mesh(geometry, material);
mesh.position.x = (Math.random() - 0.5) * 1000;
mesh.position.y = (Math.random() - 0.5) * 1000;
mesh.position.z = (Math.random() - 0.5) * 1000;
mesh.updateMatrix();
mesh.matrixAutoUpdate = false;
state.pyramids.push(mesh);
};
state.scene.add(...state.pyramids);
// create lights
var lightA = new DirectionalLight(0xffffff);
lightA.position.set(1,1,1);
state.scene.add(lightA);
var lightB = new DirectionalLight(0x002288);
lightB.position.set(-1, -1, -1);
state.scene.add(lightB);
var lightC = new AmbientLight(0x222222);
state.scene.add(lightC);
},
RESIZE(state, {width, height}){
state.width = width;
state.height = height;
state.camera.aspect = width/height;
state.camera.updateProjectionMatrix();
state.renderer.setSize(width, height);
state.controls.handleResize();
state.renderer.render(state.scene, state.camera);
}
};
const actions = {
INIT({ commit, state }, { width, height, el}){
return new Promise(resolve => {
commit("SET_VIEWPORT_SIZE", { width, height });
commit("INITIALIZE_RENDERER", el);
commit("INITIALIZE_CAMERA");
commit("INITIALIZE_CONTROLS");
commit("INITIALIZE_SCENE");
state.renderer.render(state.scene, state.camera);
state.controls.addEventListener("change", () => {
state.renderer.render(state.scene, state.camera);
});
resolve();
});
},
ANIMATE({ dispatch, state }){
window.requestAnimationFrame(() => {
dispatch("ANIMATE");
state.controls.update();
});
}
}
export default {
state,
mutations,
actions
};
问题是传递给 threejs
初始化的对象附加到 Vuex 状态,并且 threejs
在内部修改对象,导致您观察到的警告。
但是,将对象附加到 Vuex 状态还有另一个副作用,即在使对象具有反应性时会导致大量开销。 ThreeJS 创建了许多内部属性,这些属性都将成为响应式的。这就是应用程序启动严重延迟的原因。
解决方案是使用 markRaw()
API 将它们标记为原始数据(因此不需要反应性)。 ThreeJS 仍然可以将属性附加到给定的对象,而无需在它们之间创建反应性连接:
// store/main_three.js
import { markRaw } from "vue";
⋮
export default {
⋮
mutations: {
INITIALIZE_RENDERER(state, el) {
const renderer = new WebGLRenderer(⋯);
⋮
state.renderer = markRaw(renderer);
⋮
},
INITIALIZE_CAMERA(state) {
const camera = new PerspectiveCamera(⋯);
⋮
state.camera = markRaw(camera);
},
INITIALIZE_CONTROLS(state) {
const controls = new TrackballControls(⋯);
⋮
state.controls = markRaw(controls);
},
INITIALIZE_SCENE(state) {
const scene = new Scene();
⋮
state.scene = markRaw(scene);
⋮
for (var i = 0; i < 500; i++) {
var mesh = new Mesh(⋯);
⋮
state.pyramids.push(markRaw(mesh));
}
⋮
// create lights
var lightA = new DirectionalLight(0xffffff);
⋮
state.scene.add(markRaw(lightA));
var lightB = new DirectionalLight(0x002288);
⋮
state.scene.add(markRaw(lightB));
var lightC = new AmbientLight(0x222222);
state.scene.add(markRaw(lightC));
},
⋮
},
⋮
};
我在这里看到了不同的类似主题,但它们没有解决我的问题。我尝试将 Three.js 与 Vue3 (+Vuex) 结合使用。我彻底遵循了本教程:https://stagerightlabs.com/blog/vue-and-threejs-part-one(并且它在该站点上有效)但是我的相同代码不起作用,它抛出错误:
Uncaught (in promise) Error: [vuex] do not mutate vuex store state outside mutation handlers.
我在代码中的任何地方都看不到状态在突变处理程序之外发生突变。我不明白为什么会出现此错误。可能跟Vue3本身有关系?教程里的app估计是用Vue2写的,不知道会不会出问题
这是我的代码。抱歉,如果它看起来很长,但希望它有助于检测根本原因。这也是可重现的例子:https://codesandbox.io/s/black-microservice-y2jo6?file=/src/components/BaseModel.vue
BaseModel.vue
<template>
<div class="viewport"></div>
</template>
<script>
import { mapMutations, mapActions } from 'vuex'
export default {
name: 'BaseModel',
components: {
},
data() {
return {
height: 0
};
},
methods: {
...mapMutations(["RESIZE"]),
...mapActions(["INIT", "ANIMATE"])
},
mounted() {
this.INIT({
width: this.$el.offsetWidth,
width: this.$el.offsetHeight,
el: this.$el
})
.then(() => {
this.ANIMATE();
window.addEventListener("resize", () => {
this.RESIZE({
width: this.$el.offsetWidth,
height: this.$el.offsetHeight
});
}, true);
});
}
}
</script>
<style scoped>
.viewport {
height: 100%;
width: 100%;
}
</style>
store.js
import { TrackballControls } from 'three/examples/jsm/controls/TrackballControls.js'
import {
Scene,
PerspectiveCamera,
WebGLRenderer,
Color,
FogExp2,
CylinderBufferGeometry,
MeshPhongMaterial,
Mesh,
DirectionalLight,
AmbientLight,
} from 'three'
const state = {
width: 0,
height: 0,
camera: null,
controls: null,
scene: null,
renderer: null,
pyramids: []
};
const mutations = {
SET_VIEWPORT_SIZE(state, {width, height}){
state.width = width;
state.height = height;
},
INITIALIZE_RENDERER(state, el){
state.renderer = new WebGLRenderer({ antialias: true });
state.renderer.setPixelRatio(window.devicePixelRatio);
state.renderer.setSize(state.width, state.height);
el.appendChild(state.renderer.domElement);
},
INITIALIZE_CAMERA(state){
state.camera = new PerspectiveCamera(60, state.width/state.height, 1, 1000);
state.camera.position.set(0,0,500);
},
INITIALIZE_CONTROLS(state){
state.controls = new TrackballControls(state.camera, state.renderer.domElement);
state.controls.rotateSpeed = 1.0;
state.controls.zoomSpeed = 1.2;
state.controls.panSpeed = 0.8;
state.controls.noZoom = false;
state.controls.noPan = false;
state.controls.staticMoving = true;
state.controls.dynamicDampingFactor = 0.3;
},
INITIALIZE_SCENE(state){
state.scene = new Scene();
state.scene.background = new Color(0xcccccc);
state.scene.fog = new FogExp2(0xcccccc, 0.002);
var geometry = new CylinderBufferGeometry(0,10,30,4,1);
var material = new MeshPhongMaterial({
color: 0xffffff,
flatShading: true
});
for(var i = 0; i < 500; i++){
var mesh = new Mesh(geometry, material);
mesh.position.x = (Math.random() - 0.5) * 1000;
mesh.position.y = (Math.random() - 0.5) * 1000;
mesh.position.z = (Math.random() - 0.5) * 1000;
mesh.updateMatrix();
mesh.matrixAutoUpdate = false;
state.pyramids.push(mesh);
};
state.scene.add(...state.pyramids);
// create lights
var lightA = new DirectionalLight(0xffffff);
lightA.position.set(1,1,1);
state.scene.add(lightA);
var lightB = new DirectionalLight(0x002288);
lightB.position.set(-1, -1, -1);
state.scene.add(lightB);
var lightC = new AmbientLight(0x222222);
state.scene.add(lightC);
},
RESIZE(state, {width, height}){
state.width = width;
state.height = height;
state.camera.aspect = width/height;
state.camera.updateProjectionMatrix();
state.renderer.setSize(width, height);
state.controls.handleResize();
state.renderer.render(state.scene, state.camera);
}
};
const actions = {
INIT({ commit, state }, { width, height, el}){
return new Promise(resolve => {
commit("SET_VIEWPORT_SIZE", { width, height });
commit("INITIALIZE_RENDERER", el);
commit("INITIALIZE_CAMERA");
commit("INITIALIZE_CONTROLS");
commit("INITIALIZE_SCENE");
state.renderer.render(state.scene, state.camera);
state.controls.addEventListener("change", () => {
state.renderer.render(state.scene, state.camera);
});
resolve();
});
},
ANIMATE({ dispatch, state }){
window.requestAnimationFrame(() => {
dispatch("ANIMATE");
state.controls.update();
});
}
}
export default {
state,
mutations,
actions
};
问题是传递给 threejs
初始化的对象附加到 Vuex 状态,并且 threejs
在内部修改对象,导致您观察到的警告。
但是,将对象附加到 Vuex 状态还有另一个副作用,即在使对象具有反应性时会导致大量开销。 ThreeJS 创建了许多内部属性,这些属性都将成为响应式的。这就是应用程序启动严重延迟的原因。
解决方案是使用 markRaw()
API 将它们标记为原始数据(因此不需要反应性)。 ThreeJS 仍然可以将属性附加到给定的对象,而无需在它们之间创建反应性连接:
// store/main_three.js
import { markRaw } from "vue";
⋮
export default {
⋮
mutations: {
INITIALIZE_RENDERER(state, el) {
const renderer = new WebGLRenderer(⋯);
⋮
state.renderer = markRaw(renderer);
⋮
},
INITIALIZE_CAMERA(state) {
const camera = new PerspectiveCamera(⋯);
⋮
state.camera = markRaw(camera);
},
INITIALIZE_CONTROLS(state) {
const controls = new TrackballControls(⋯);
⋮
state.controls = markRaw(controls);
},
INITIALIZE_SCENE(state) {
const scene = new Scene();
⋮
state.scene = markRaw(scene);
⋮
for (var i = 0; i < 500; i++) {
var mesh = new Mesh(⋯);
⋮
state.pyramids.push(markRaw(mesh));
}
⋮
// create lights
var lightA = new DirectionalLight(0xffffff);
⋮
state.scene.add(markRaw(lightA));
var lightB = new DirectionalLight(0x002288);
⋮
state.scene.add(markRaw(lightB));
var lightC = new AmbientLight(0x222222);
state.scene.add(markRaw(lightC));
},
⋮
},
⋮
};