Vue 3 外部状态反应性

问题描述 投票:0回答:1

我有一个模式,用来尝试在 vanilla js 中保留尽可能多的逻辑(单元测试、数据获取等),并使用 Vue 来处理渲染或仅在框架中可能的任何其他功能。这样我的应用程序就尽可能与框架无关。

我有一个项目,在过去几年中从 Vue 2 到 Vue 2.6 以及组合 API 都取得了进展。本质上:我有一个普通的 js 对象,然后通过另一个“useMyObject”样式函数公开它们并将状态设置为反应式。然后,我将所有操作绑定到我的“vanilla”js 控制器,并将模板绑定到反应状态。在 vue 2.6 + compost 中,这一切顺利。

我已经轻视了以下内容,但作为一个例子:

// Vanilla JS file nick-controller.ts
export interface Person {
  id: number
  firstName: string
  lastName: string
  age: number
  address: {
    line1: string
    line2: string
    city: string
    state: string
    zip: string
  }
}

export interface NickControllerState {
  people: Person[]
  personEditing: Person
  isoTimeStamp: string
}

export function factoryNickController() {
    const state: NickControllerState = {
    people: [],
    personEditing: {
      id: 0,
      firstName: '',
      lastName: '',
      age: 0,
      address: {
        line1: '',
        line2: '',
        city: '',
        state: '',
        zip: ''
      }
    },
    isoTimeStamp: ''
  }

  function addPerson(person: Person) {
    state.people.push(person)
    state.isoTimeStamp = new Date().toISOString()
  }

  return {
    state,
    addPerson
  }
}

然后是要反应的文件(我添加了“addTestPerson”只是为了我的头发拉扯测试,但通常这个文件只有几行长)

export function useNickController() {
  const controller = factoryNickController()

  // * * * * * * * * * * * * * * * * * * * * * * * * 
  // * * * * * * * * * * * * * * * * * * * * * * * * 
  // * * * Make the vanilla js state reactive. * * *
  // * * * * * * * * * * * * * * * * * * * * * * * * 
  // * * * * * * * * * * * * * * * * * * * * * * * * 
  const state = reactive(controller.state)

  function addTestPerson() {
    addPersonCounter++

    //  create random 5 digit number
    const random5DigitNumber = Math.floor(10000 + Math.random() * 90000)

    const person: Person = {
      id: addPersonCounter,
      firstName: 'Nick_' + addPersonCounter.toString(),
      lastName: 'Benz_' + addPersonCounter.toString(),
      age: 50,
      address: {
        line1: '123 Main St',
        line2: '',
        city: 'Denver',
        state: 'CO',
        zip: random5DigitNumber.toString()
      }
    }
    controller.addPerson(person)
  }

  return {
    state,
    controller,
    addTestPerson,
  }
}

然后创建一个单文件组件,在“useFunction”中添加...

<template>
  <div>
    <button @click="nickController.addTestPerson()">Add</button>

    <ul v-for="person in nickController.state.people" :key="person.id">
      <li>id: {{ person.id + person.firstName + '-' + person.lastName }}</li>
    </ul>

  </div>
</template>

<script lang="ts">
import { defineComponent, type SetupContext } from 'vue'
import { useNickController } from '@/modules/nick/useNickController'

export default defineComponent({
  name: 'NickTestControllerPage',
  components: {},
  props: {},
  setup(props: any, context: SetupContext) {
    let nickController = useNickController()

    return { nickController }
  }
})
</script>

按下添加按钮或以任何方式更改状态,模板重新渲染...完美。

我现在参与了一个Vue 3项目,如果我在“defineComponent”SFC中使用上述方法或者使用Vue首选的“setup”方法...

<script setup lang="ts">
import { useNickController } from '@/modules/nick/useNickController'
import {
  factoryNickController,
  type NickControllerState,
  type Person
} from '@/modules/nick/nick-controller'
import { reactive} from 'vue'

const nickController = useNickController()


function addTestPerson() {
  //  create random 5 digit number
  const random5DigitNumber = Math.floor(10000 + Math.random() * 90000)
  ...etc...etc...
  nickController.state.people.push(person)
}
</script>

...它不会更新,直到“下一个刻度”渲染。例如。在页面上粘贴一个绑定到某个局部变量的输入字段...

const myField = ref("")

...更改字段值,触发重新渲染...然后渲染我的反应式香草状态下的 v-for。

我已经尝试了我能想到的所有不同方法:计算、观察者、组件中的普通对象......我无法让它工作。

重申一下:在 vue 2.6 + 组合 api 中,这非常有效。

我们将非常欢迎任何帮助或指点。预先感谢。

vue.js vuejs2 vuejs3 reactive
1个回答
0
投票

Vue 2 修改对象以便做出反应,并为现有属性添加 getter/setter 对,如 documentation 中所述,

reactive
必须在 2.7 组合 API 中使用此机制。 Vue 3 用
Proxy
包裹一个对象,并保持原始对象完好无损。

这是 Vue 中反应式类的常见陷阱。尽管该示例没有使用类,但问题是相同的;对非反应性原始对象的引用被保留和修改,并且无法跟踪更改。解决方案也是一样的,反应状态应该一致引用,最直接的方法是使用

this
代替非反应
state
变量:

  ...
  return {
    state,
    addPerson(person: Person) {
      // this.state !== state in Vue 3
      this.state.people.push(person)
      this.state.isoTimeStamp = new Date().toISOString()
    }
  }
  ...

以这种方式定义方法时,

this
可以正确推断 TS 类型。

使用方式如下:

const controller = reactive(factoryNickController());

不同之处在于,用

addPerson(...)
代替
controller.addPerson(...)
会导致上下文错误,需要重新绑定方法才能允许这种用法。

© www.soinside.com 2019 - 2024. All rights reserved.