Repository: effector/effector
Pull request #417 (2020-11-08 05:49:56)

effector-vue 21.1.0
Add support vue 3


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 171c2402..b5144afb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,11 @@
 
 See also [separate changelogs for each library](https://changelog.effector.dev/)
 
+## effector-vue 21.1.0
+
+- Add support for vue@next (vue 3)
+- `useStore` `useVModel` `useGate` `createGate` [see docs](https://effector.dev/docs/api/effector-vue/effector-vue)
+
 ## effector 21.6.0
 
 - Add support for user-defined factories in fork api. Starting from this release, application developers can use their own functions and be sure that their content will be properly serialized and hydrated by fork api.
diff --git a/docs/api/effector-vue/index.md b/docs/api/effector-vue/index.md
index 945b809c..3955a23e 100644
--- a/docs/api/effector-vue/index.md
+++ b/docs/api/effector-vue/index.md
@@ -22,7 +22,7 @@ title: API Reference
 ### Hooks
 
 - [useStore(store)](./useStore.md)
-- [useModel(store)](./useModel.md)
+- [useVModel(store)](./useVModel.md)
 
 ### Gate API
 
diff --git a/docs/api/effector-vue/useModel.md b/docs/api/effector-vue/useVModel.md
similarity index 86%
rename from docs/api/effector-vue/useModel.md
rename to docs/api/effector-vue/useVModel.md
index 125cd825..dfb807bd 100644
--- a/docs/api/effector-vue/useModel.md
+++ b/docs/api/effector-vue/useVModel.md
@@ -1,10 +1,10 @@
 ---
-id: useModel
-title: useModel
+id: useVModel
+title: useVModel
 hide_title: true
 ---
 
-# `useModel(store)`
+# `useVModel(store)`
 
 Creates a hook function, which subscribes to watcher, that observes changes in the current store, so when recording results, the component will update automatically.
 Basically this hook use when need to work with forms (`v-model`)
@@ -21,7 +21,7 @@ Basically this hook use when need to work with forms (`v-model`)
 
 ```js
 import {createStore, createApi} from 'effector'
-import {useModel} from 'effector-vue/composition'
+import {useVModel} from 'effector-vue/composition'
 
 const $user = createStore({
   name: '',
@@ -31,7 +31,7 @@ const $user = createStore({
 
 export default {
   setup() {
-    const user = useModel($user);
+    const user = useVModel($user);
 
     return {
       user
diff --git a/packages/effector-vue/composition.d.ts b/packages/effector-vue/composition.d.ts
index 27a71f01..27ebcb14 100644
--- a/packages/effector-vue/composition.d.ts
+++ b/packages/effector-vue/composition.d.ts
@@ -18,6 +18,6 @@ type Gate<Props> = {
 };
 
 export function useStore<T>(store: Store<T>): DeepReadonly<UnwrapNestedRefs<T>>
-export function useModel<T>(store: Store<T>): Ref<UnwrapRef<T>>
+export function useVModel<T>(store: Store<T>): Ref<UnwrapRef<T>>
 export function createGate<Props>(config?: GateConfig<Props>): Gate<Props>
 export function useGate<Props>(GateComponent: Gate<Props>, cb?: () => Props): void
\ No newline at end of file
diff --git a/packages/effector-vue/package.json b/packages/effector-vue/package.json
index 59598d8e..530f26ab 100644
--- a/packages/effector-vue/package.json
+++ b/packages/effector-vue/package.json
@@ -1,6 +1,6 @@
 {
   "name": "effector-vue",
-  "version": "21.0.3",
+  "version": "21.1.0",
   "description": "Vue bindings for effector",
   "main": "effector-vue.cjs.js",
   "module": "effector-vue.mjs",
diff --git a/packages/forest/package.json b/packages/forest/package.json
index 23c364a4..c6c71a96 100644
--- a/packages/forest/package.json
+++ b/packages/forest/package.json
@@ -21,7 +21,7 @@
   "typings": "index.d.ts",
   "dependencies": {},
   "peerDependencies": {
-    "effector": "^21.6.1"
+    "effector": "^21.6.2"
   },
   "keywords": [
     "data",
diff --git a/src/vue/composition.ts b/src/vue/composition.ts
index 99390de1..cb3e2428 100644
--- a/src/vue/composition.ts
+++ b/src/vue/composition.ts
@@ -1,3 +1,3 @@
 export {useStore} from './useStore'
-export {useModel} from './useModel'
+export {useVModel} from './useVModel'
 export {createGate, useGate} from './createGate'
\ No newline at end of file
diff --git a/src/vue/createGate.ts b/src/vue/createGate.ts
index 8de39713..2dd8d087 100644
--- a/src/vue/createGate.ts
+++ b/src/vue/createGate.ts
@@ -1,5 +1,4 @@
-import {computed, ComputedRef} from '@vue/reactivity'
-import {onMounted, onUnmounted, watch, WatchStopHandle} from '@vue/runtime-core'
+import {computed, ComputedRef, onMounted, onUnmounted, watch, WatchStopHandle} from 'vue-next'
 import {createApi, launch, createStore} from 'effector'
 import {Gate, GateConfig} from './composition.h'
 import {deepCopy} from './lib/deepCopy'
diff --git a/src/vue/lib/unwrapProxy.ts b/src/vue/lib/unwrapProxy.ts
index 2b94268d..5e84401f 100644
--- a/src/vue/lib/unwrapProxy.ts
+++ b/src/vue/lib/unwrapProxy.ts
@@ -1,4 +1,4 @@
-import {isReactive, toRaw, unref} from '@vue/reactivity'
+import {isReactive, toRaw, unref} from 'vue-next'
 
 export function unwrapProxy<T>(payload: T) {
   const data = unref(payload)
diff --git a/src/vue/useStore.ts b/src/vue/useStore.ts
index 1bc75354..2f7358ef 100644
--- a/src/vue/useStore.ts
+++ b/src/vue/useStore.ts
@@ -1,14 +1,13 @@
 import {Store, Subscription} from 'effector'
-import {readonly, shallowRef} from '@vue/reactivity'
-import {onMounted, onUnmounted} from '@vue/runtime-core'
+import {onMounted, onUnmounted, readonly, shallowRef} from 'vue-next'
 
 export function useStore<T>(store: Store<T>) {
   let unwatch: Subscription
   const _ = shallowRef(store.getState())
 
   onMounted(() => {
-    unwatch = store.watch(value => {
-      _.value = value
+    unwatch = store.updates.watch(value => {
+      _.value = shallowRef(value).value
     })
   })
 
diff --git a/src/vue/useModel.ts b/src/vue/useVModel.ts
similarity index 73%
rename from src/vue/useModel.ts
rename to src/vue/useVModel.ts
index e6deb80e..adeaf1a6 100644
--- a/src/vue/useModel.ts
+++ b/src/vue/useVModel.ts
@@ -1,10 +1,10 @@
 import {Store, Subscription} from 'effector'
-import {onMounted, onUnmounted, ref, watch} from '@vue/runtime-core'
+import {onMounted, onUnmounted, ref, watch} from 'vue-next'
 import {unwrapProxy} from './lib/unwrapProxy'
 import {deepCopy} from './lib/deepCopy'
 
-export function useModel<T>(store: Store<T>) {
-  const _ = ref(store.getState())
+export function useVModel<T>(store: Store<T>) {
+  const _ = ref(deepCopy(store.getState()))
 
   let isSelfUpdate = false
   let fromEvent = false
@@ -17,8 +17,7 @@ export function useModel<T>(store: Store<T>) {
       }
 
       fromEvent = true
-      // @ts-ignore
-      _.value = deepCopy(payload)
+      _.value = ref(deepCopy(payload)).value
     })
   })
 
@@ -32,7 +31,7 @@ export function useModel<T>(store: Store<T>) {
       isSelfUpdate = true
 
       if (!fromEvent) {
-        const raw = unwrapProxy(value)
+        const raw = ref(unwrapProxy(value)).value
         // @ts-ignore
         store.setState(deepCopy(raw))
       }
diff --git a/tools/builder/packages.config.js b/tools/builder/packages.config.js
index d0e06761..1f778ec1 100644
--- a/tools/builder/packages.config.js
+++ b/tools/builder/packages.config.js
@@ -38,7 +38,7 @@ const keywords = [
 const version = {
   effector: '21.6.2',
   'effector-react': '21.0.8',
-  'effector-vue': '21.0.3',
+  'effector-vue': '21.1.0',
   forest: '0.19.4',
 }
 
diff --git a/website/client/sidebars.js b/website/client/sidebars.js
index cfb57938..5bcbbed6 100644
--- a/website/client/sidebars.js
+++ b/website/client/sidebars.js
@@ -113,6 +113,22 @@ const sidebar = {
         'api/effector-vue/effector-vue',
         'api/effector-vue/vue-effector',
         'api/effector-vue/component-options',
+        {
+          type: 'category',
+          label: 'Gate API',
+          items: [
+            'api/effector-vue/createGate',
+            'api/effector-vue/useGate',
+          ],
+        },
+        {
+          type: 'category',
+          label: 'Hooks',
+          items: [
+            'api/effector-vue/useStore',
+            'api/effector-vue/useVModel',
+          ],
+        },
         'api/effector-vue/vue',
       ],
     },


Test patch:
diff --git a/src/vue/__tests__/createGate.test.ts b/src/vue/__tests__/createGate.test.ts
index dfa4402c..e357ee4d 100644
--- a/src/vue/__tests__/createGate.test.ts
+++ b/src/vue/__tests__/createGate.test.ts
@@ -1,7 +1,7 @@
 /* eslint-disable react-hooks/rules-of-hooks */
 import {shallowMount} from 'vue-test-utils-next'
 import {useStore, useGate, createGate} from 'effector-vue/composition'
-import {reactive, ref} from '@vue/reactivity'
+import {reactive, ref} from 'vue-next'
 import {createEvent, createStore} from 'effector'
 
 jest.mock('vue', () => require('vue-next'))
diff --git a/src/vue/__tests__/useModel.test.ts b/src/vue/__tests__/useVModel.test.ts
similarity index 77%
rename from src/vue/__tests__/useModel.test.ts
rename to src/vue/__tests__/useVModel.test.ts
index aa6eb977..21608116 100644
--- a/src/vue/__tests__/useModel.test.ts
+++ b/src/vue/__tests__/useVModel.test.ts
@@ -1,5 +1,5 @@
 /* eslint-disable react-hooks/rules-of-hooks */
-import {useModel} from 'effector-vue/composition'
+import {useVModel} from 'effector-vue/composition'
 import {createEvent, createStore} from 'effector'
 import {shallowMount} from 'vue-test-utils-next'
 
@@ -23,7 +23,7 @@ it('updated value of input if store changed from outside', async () => {
       </div>
     `,
     setup() {
-      const user = useModel($user)
+      const user = useVModel($user)
       return {user}
     }
   })
@@ -45,7 +45,7 @@ it('[v-model] works correct with scalar values', async () => {
       <div><input v-model="username" data-test="field"></div>
     `,
     setup() {
-      const username = useModel($username)
+      const username = useVModel($username)
       return {username}
     }
   })
@@ -67,7 +67,7 @@ it('[v-model] works correct with objects', async () => {
       </div>
     `,
     setup() {
-      const user = useModel($user)
+      const user = useVModel($user)
       return {user}
     }
   })
@@ -80,6 +80,34 @@ it('[v-model] works correct with objects', async () => {
   })
 })
 
+it('reset useVModel', async () => {
+  const model = {
+    name: '',
+    surname: '',
+  }
+  const reset = createEvent()
+  const $user = createStore(model).reset(reset)
+
+  const wrapper = shallowMount({
+    template: `
+      <div>
+        <input v-model="user.name" data-test="name">
+        <input v-model="user.surname" data-test="surname">
+      </div>
+    `,
+    setup() {
+      const user = useVModel($user)
+      return {user}
+    }
+  })
+
+  await wrapper.find('[data-test="name"]').setValue('John')
+  await wrapper.find('[data-test="surname"]').setValue('Doe')
+  reset()
+  await wrapper.vm.$nextTick()
+  expect($user.getState()).toEqual(model)
+})
+
 it('[v-model] works correct with checkboxes (like vue-3 way)', async () => {
   const $skills = createStore([])
 
@@ -90,7 +118,7 @@ it('[v-model] works correct with checkboxes (like vue-3 way)', async () => {
       <input type="checkbox" v-model="skills" data-test="skills" value="JS">
     `,
     setup() {
-      const skills = useModel($skills)
+      const skills = useVModel($skills)
       return {skills}
     }
   })
@@ -110,7 +138,7 @@ it('[v-model] works correct with radio (like vue-3 way)', async () => {
       <input type="radio" name="gender" v-model="gender" data-test="gender" value="male">
     `,
     setup() {
-      const gender = useModel($gender)
+      const gender = useVModel($gender)
       return {gender}
     }
   })