第5期 - 夜游西湖

封面图来源于周六夜晚的西湖,吹着微风漫步在湖边,还是很惬意的。

湖面的倒影

技术分享-前端数据流相关

Mobx

状态变化时自动更新相关的视图组件,注重数据的驱动,数据的变化驱动界面的更新,不依赖模板引擎,可以使用普通的 JSX 或 HTML 模板来渲染视图。

核心 API observable、computed、action 和 reaction

observable

定义一个存储 state 的可跟踪字段。如果可能,任何被赋值给 observable 的字段都会基于它自己的类型被(深度)转化为 observable 递归的将整个对象变成可观察的,通常由类构造函数负责将类成员转化成可观察对象,observable 会创建一个可观察的 副本 对象,可以捕获将要添加的属性。(如果对象具有一个常规结构,其中所有的成员都是事先已知的更建议使用 makeObservable,速度稍快一些)

import { observable } from "mobx";

const todoStore = observable({
  todosById: {
    "TODO-123": {
      title: "find a decent task management system",
      done: false
    }
  }
});

// 装饰器写法
class TodoStore {
  @observable
  todosById = {
    "TODO-123": {
      title: "find a decent task management system",
      done: false
    }
  };
}

action

将一个方法标记为可以修改 state 的 action action 就是任意一段修改 state 的代码,默认情况下,不允许在 actions 之外改变 state,如果一个类方法会修改 state,可以将其标记为 action。不可追踪 的,

import { observable, action } from "mobx"

const state = observable({ value: 0 })

const increment = action(state => {
    state.value++
    state.value++
})

increment(state)

action.bound 将方法自动绑定到正确的实例,这样 this 会始终被正确绑定在函数内部。想要将单个的 action 绑定 到 this,可以使用 action.bound 代替箭头函数。

@action.bound
  setPublicParams(keyword: string) {
    this.publickeyword = keyword;
  }

runInAction 创建一个会被立即调用的临时 action,通常用于 await 之后。在异步进程中更新可观察对象的每个步骤(tick)都应该被标识为 action

import { observable, runInAction } from "mobx"

const state = observable({ value: 0 })

runInAction(() => {
    state.value++
    state.value++
})

computed

标记一个可以由 state 派生出新的值并且缓存其输出的 getter 计算值采用惰性求值,会缓存其输出,并且只有当其依赖的可观察对象被改变时才会重新计算。 如果某些计算属性没有被任何 reaction 使用,MobX 将会自动挂起不活动的计算值 以避免不必要地更新未访问的计算值。 参数项 keepAlive:避免计算值在未被观察时被暂时停用;requiresReaction:在非常昂贵的计算值中将这个选项设置为 true


class Box {
    width = 0
    height = 0

    constructor() {
        makeObsevable(this, {
            x: observable,
            y: observable,
            topRight: computed
        })
    }

    get topRight() {
        return {
            x: this.width,
            y: this.height
        }
    }
}

@computed
  get mounted() {
    return this.mountState === BoardMountState.Mounted;
  }

reaction

可以将 MobX 中所有的特性有机地融合在一起。 reactions 的目的是对自动发生的副作用进行建模。 它们的意义在于为你的可观察状态创建消费者,以及每当关联的值发生变化时,自动运行副作用

reaction(() => value, (value, previousValue, reaction) => { sideEffect }, options?). 在 data 函数中返回在副作用中需要的所有数据, 并以这种方式更精确地控制副作用触发的时机

一些注意点: 1.只有在引起副作用的一方与副作用之间没有直接关系的情况下才使用 reaction 2.reactions 不应该更新其他可观察对象:例如,如果一个待办事项的集合 todos 发生了变化,那么请不要使用 reaction 来计算剩余待办 remainingTodos 的数量,reaction 不应该计算生成新的数据,而只应该触发副作用 3.reactions 应该是独立的不可相互嵌套

import { makeAutoObservable, reaction } from "mobx"

reaction(
    () => giraffe.isHungry,
    isHungry => {
        if (isHungry) {
            console.log("Now I'm hungry!")
        } else {
            console.log("I'm not hungry!")
        }
        console.log("Energy level:", giraffe.energyLevel)
    }
)
Autorun

autorun(effect: (reaction) => void) 接受一个函数作为参数,每当该函数所观察的值发生变化时(observable 或者 computed 注释的),它都应该运行

在给定的函数执行期间,MobX 会持续跟踪被 effect 直接或间接读取过的所有可观察对象和计算值。 一旦函数执行完毕,MobX 将收集并订阅所有被读取过的可观察对象,并等待其中任意一个再次发生改变。 一旦有改变发生,autorun 将会再次触发,重复整个过程。

autorun(() => {
    console.log("Energy level:", giraffe.energyLevel)
})
When

when(predicate: () => boolean, effect?: () => void, options?) when(predicate: () => boolean, options?): Promise when 会观察并运行给定的 predicate 函数,直到其返回 true。一旦 predicate 返回了 true,给定的 effect 函数就会执行并且自动执行器函数将会被清理掉。 如果没有传入 effect 函数,when 函数返回一个 Promise 类型的 disposer,并允许你手动取消。

get isVisible() {
 }
dispose() {
     // 清理一些资源.
 }
when(
            // Once...
            () => !this.isVisible,
            // ... then.
            () => this.dispose()
)

// 异步
async function() {
    await when(() => that.isVisible)
    // etc...
}

Vuex

核心是 store 容器, 每个应用仅包含一个 store 实例 响应式状态存储:从 store 中读取状态,若 store 中的状态发生变化,相应的组件更新

state

状态值读取:包含应用的公共状态state,从 store 实例中读取状态最简单的方法就是在计算属性中返回

const moduleState = {
  doneTodosCount: 1,  // 定义最初的初始值
}
// 访问
get count () {
      return this.$store.state.count
}

Getters

从 store 中的 state 中派生出一些状态, 通过 mutations、Actions、等等一系列操作的state 都是由它获取,可以定义一个单独的文件去统一管理所有 state,由 getters 获取,注入 Vuex.Store 全局中

// 定义
getters: {
  doneTodosCount: (state, getters) => {
    return getters.doneTodos.length
  }
}
// 使用
computed: {
  doneTodosCount () {
    return this.$store.getters.doneTodosCount
  }
}

  // 统一管理
  this.$store.state.app.doneTodosCount  // 管理前调用
  
  const getters = {
  count: state => state.app.doneTodosCount,
  }
  // mapGetters
  computed: {
  // 使用对象展开运算符将 getters 混入 computed 对象中
    ...mapGetters([
      'doneTodosCount',  // 管理后直接通过 mapGetters 调用
      'anotherGetter',
      // ...
    ])
  }

mutations

更改 Vuex 的 store 中的状态的唯一方法是提交 mutation

Actions

Action 通过调用 commit 提交 mutation 再通过 在组件中使用 this.$store.dispatch(‘xxx’) 分发 action 来触发变更状态(感觉是个操蛋sb的流程),支持异步。

说人话就是 先定义一个 actions,然后提交commit 一个 mutations 事件,去执行对应的事件,通过执行 dispatch 去调用之前定义的 actions 方法,完成整个状态流的数据变更

const actions = {
  // 定义一个 actions
  increment ({ commit }) {
    commit('increment_count')
  }
}
const mutations = {
  // mutations 事件
  increment_count: state => {
    state.count++
  }
}
// 执行
store.dispatch('app/increment') // app/xxx 是你定义actions的文件路径,通过路径找到对应方法action执行

module

定义属于模块内部的 mutation、actions 和 getter