Игорь Шеко, Voximplant
Место стейта в будущем vue.js
ВНИМАНИЕ!
В данной презентации содержится исходный код. Для удобства просмотра, вы можете воспользоваться ссылкой на live трансляцию этого презентации.
function helloWorld(hello, world){
return hello + " " + word;
}
helloWorld("Hello". "world!")
ВНИМАНИЕ!
В данной презентации содержится исходный код. Для удобства просмотра, вы можете воспользоваться ссылкой на live трансляцию этого презентации.
Поговорим о том, о чем во фронтенде обычно не говорят.
О программировании.
Давным давно, в далекой
далекой галактике...
PubSub
export default class PubSub{
private listeners:Map<string,EventListener[]> = new Map();
addEventListener(event:string,listener:EventListener){
const listeners = this.listenerts.get(event)||[];
this.listenerts.set([...listeners, listener]);
}
removeEventListener(event:string,listener?:EventListener){
if(!listener)
this.listenerts.remove(event);
this.listenerts.set(this.listeners.filter(item=>item!==listener]);
}
dispatch(event:string, data?:any){
const listeners = this.listenerts.get(event)||[];
listeners.forEach(item=>item.handleEvent(data));
}
}
И незачем каждый раз наследовать от Vue.
Event "A" received
Component 1
Component 2
Component 3
Component 4
PubSub
Event "A" received
Event "A" dispatched
Component 1
Component 2
Component 3
Component 4
PubSub
Очень быстро
Не реактивно
Надо отписываться
А что если...
Component 1
Component 2
Component 3
Component 4
PubSub
DATA
Event "Change A" received
Component 1
Component 2
Component 3
Component 4
PubSub
DATA
Event "Change A" received
Event "Change A" dispatched
Component 1
Component 2
Component 3
Component 4
PubSub
DATA
Event "Change A" received
Event "Change A" dispatched
Event "Changed A" received
Component 1
Component 2
Component 3
Component 4
PubSub
DATA
Event "Change A" received
Event "Change A" dispatched
Event "Changed A" received
Event "Changed A" dispatched
Component 1
Component 2
Component 3
Component 4
PubSub
DATA
Ничего не напоминает?
Flux-библиотеки похожи на очки: вы будете точно знать, когда они вам понадобятся.
Действие
Состояние
Представление
Строгость
Шаблонность
Краткость
Действие
Состояние
Представление
Компонент
Действие
Состояние
Представление
Компонент
Мутация
Действие
Состояние
Представление
Компонент
Мутация
Состояние
Действие
Состояние
Представление
Компонент
Мутация
Состояние
Геттер
Действие
Состояние
Представление
Компонент
Мутация
Состояние
Геттер
Модуль User
Модуль Auth
Модуль Conversations
Модуль Notifications
Действие
Состояние
Представление
Компонент
Мутация
Состояние
Геттер
Модуль User
Модуль Auth
Модуль Conversations
Модуль Notifications
Middleware
Строгость?
Шаблонность?
Краткость?
Главная проблема - сложность!
import { mapGetters } from 'vuex';
export default {
// ...
computed: {
// смешиваем результат mapGetters с внешним объектом computed
...mapGetters([
'doneTodosCount',
'anotherGetter'
// ...
])
}
};
/**
* Reduce the code which written in Vue.js for getting the getters
* @param {String} [namespace] - Module's namespace
* @param {Object|Array} getters
* @return {Object}
*/
export const mapGetters = normalizeNamespace((namespace, getters) => {
const res = {}
normalizeMap(getters).forEach(({ key, val }) => {
// The namespace has been mutated by normalizeNamespace
val = namespace + val
res[key] = function mappedGetter () {
if (namespace && !getModuleByNamespace(this.$store, 'mapGetters', namespace)) {
return
}
if (process.env.NODE_ENV !== 'production' && !(val in this.$store.getters)) {
console.error(`[vuex] unknown getter: ${val}`)
return
}
return this.$store.getters[val]
}
// mark vuex getter for devtools
res[key].vuex = true
})
return res
})
Вторая проблема - store слабо связан с приложением.
Части store проблематично наследовать.
Но, в любом случае, это лучшее, что у нас есть сейчас
Декомпозиция
не кладем все яйца в один store
Реактивность in the wild
<template>
<button @click="count++">счётчик — {{count}}</button>
<template>
<script lang="ts">
import {Component, Prop, Vue} from "vue-property-decorator";
@Component({
components: {},
})
export default class NoopButton extends Vue {
count:number = 0;
}
</script>
const state = Vue.observable({ count: 0 })
const Demo = {
render(h) {
return h('button', {
on: { click: () => { state.count++ }}
}, `счётчик — ${state.count}`)
}
}
const state = Vue.observable({ count: 0 })
const Demo = {
render(h) {
return h('button', {
on: { click: () => { state.count++ }}
}, `счётчик — ${state.count}`)
}
}
const Demo2 = {
render(h) {
return h('button', {
on: { click: () => { state.count++ }}
}, `счётчик2 — ${state.count}`)
}
}
Подробнее про разные взаимодействия.
https://www.youtube.com/channel/UCp4bYTGrxZ27ifRxm29U5ow
И понемногу ваше приложение начинает дышать...
И не обязательно все должно быть реактивным.
Proxy
Как работает реактивность.
Сейчас.
В коде.
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
Тоже самое, но с Proxy.
data = new Proxy(data_without_proxy, { // Override data to have a proxy in the middle
get(obj, key) {
deps.get(key).depend(); // <-- Remember the target we're running
return obj[key]; // call original data
},
set(obj, key, newVal) {
obj[key] = newVal; // Set original data to new value
deps.get(key).notify(); // <-- Re-run stored functions
return true;
}
});
Evan You on Proxies
2.5
3.x
Рендеринг 3000 state-full компонентов
Composition API
<template>
<form @submit="handleSubmit">
<label>
<span>Note:</span>
<input v-model="currentNote" @input="handleNoteInput">
</label>
<button type="submit">Send</button>
</form>
</template>
<script>
import { ref, watch } from "vue";
export default {
props: ["divRef"],
setup(props, context) {
const currentNote = ref("");
const handleNoteInput = e => {
const val = e.target.value && e.target.value.toUpperCase()[0];
const validNotes = ["A", "B", "C", "D", "E", "F", "G"];
currentNote.value = validNotes.includes(val) ? val : "";
};
const handleSubmit = e => {
context.emit("note-sent", currentNote.value);
currentNote.value = "";
e.preventDefault();
};
return {
currentNote,
handleNoteInput,
handleSubmit,
};
}
};
</script>
export const outerCount = ref(0)
// где-то в компоненте Example.vue
<template>
<button @click="onClick">счётчик — {{count}}</button>
<template>
<script>
import {outerCount} from "foo.js"
const Example = {
setup(props, { attrs }) {
const count = outerCount;
function onClick() {
count.value++;
}
}
}
</script>
const obj = reactive({ count: 0 })
const count = ref(0)
ref vs reactive
// стиль 1: отдельные значения
let x = 0
let y = 0
function updatePosition(e) {
x = e.pageX
y = e.pageY
}
// --- сравниваем с ---
// стиль 2: объект
const pos = {
x: 0,
y: 0
}
function updatePosition(e) {
pos.x = e.pageX
pos.y = e.pageY
}
function useMousePosition() {
const pos = reactive({
x: 0,
y: 0
})
// ...
return pos
}
export default {
setup() {
// реактивность потеряна!
const { x, y } = useMousePosition()
return {
x,
y
}
// реактивность потеряна!
return {
...useMousePosition()
}
// реактивность не потеряна
return {
pos: useMousePosition()
}
}
}
function useMousePosition() {
const pos = reactive({
x: 0,
y: 0
})
// ...
return toRefs(pos)
}
// x & y are now refs!
const { x, y } = useMousePosition()
const count = ref(0)
watch(() => console.log(count.value))
// -> logs 0
setTimeout(() => {
count.value++
// -> logs 1
}, 100)
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
/* ... */
})
const stop = watch(() => { /* ... */ })
// later
stop()
Всем спасибо
Игорь Шеко
Lead Front end developer @ Voximplant
Future of the state
By Igor Sheko
Future of the state
- 362