Bootstrap

vue export 模型_Vue通用模型

vue export 模型

Vue通用模型 (Universal Model for Vue)

Universal model is a model which can be used with any of following UI frameworks:

通用模型是可以与以下任何UI框架一起使用的模型:

  • Angular 2+ universal-model-angular

    角2+通用模型角

  • React 16.8+ universal-model-react

    React 16.8+通用模型React

  • Svelte 3+ universal-model-svelte

    Svelte 3+通用模型-svelte

  • Vue.js 3+

    Vue.js 3+

If you want to use multiple UI frameworks at the same time, you can use single model with universal-model library

如果要同时使用多个UI框架,则可以将单个模型与通用模型库一起使用

安装 (Install)

npm install --save universal-model-vue

通用模型的前提条件 (Prerequisites for universal-model-vue)

"vue": "^3.0.0-alpha.1"

干净的UI架构 (Clean UI Architecture)

mvc

  • Model-View-Controller (https://en.wikipedia.org/wiki/Model–view–controller)

    模型视图控制器( https://en.wikipedia.org/wiki/Model-view-controller )

  • User triggers actions by using view or controller

    用户使用视图或控制器触发动作

  • Actions are part of model and they manipulate state that is stored

    动作是模型的一部分,它们操纵存储的状态

  • Actions can use services to interact with external (backend) systems

    动作可以使用服务与外部(后端)系统进行交互

  • State changes trigger view updates

    状态更改触发视图更新

  • Selectors select part of state and optionally calculate a transformed version of state that causes view updates

    选择器选择状态的一部分,并选择计算导致视图更新的状态的转换版本

  • Views contain NO business logic

    视图不包含任何业务逻辑

  • There can be multiple interchangeable views that use same part of model

    可以有多个使用模型相同部分的可互换视图

  • A new view can be created to represent model differently without any changes to model

    可以创建一个新视图来以不同的方式表示模型,而无需对模型进行任何更改

  • View technology can be changed without changes to the model

    无需更改模型即可更改视图技术

干净的UI代码目录布局 (Clean UI Code directory layout)

UI application is divided into UI components. Common UI components should be put into common directory. Each component can consist of subcomponents. Each component has a view and optionally controller and model. Model consists of actions, state and selectors. In large scale apps, model can contain sub-store. Application has one store which is composed of each components' state (or sub-stores)

UI应用程序分为UI组件。 通用UI组件应放在通用目录中。 每个组件都可以包含子组件。 每个组件都有一个视图以及可选的控制器和模型。 模型由动作,状态和选择器组成。 在大型应用程序中,模型可以包含子商店。 应用程序具有一个存储,该存储由每个组件的状态(或子存储)组成

- src
  |
  |- common
  |  |- component1
  |  |- component2
  |  .  |- component2_1
  |  .  .
  |  .  .
  |  .
  |- componentA
  |- componentB
  |  |- componentB_1
  |  |- componentB_2
  |- componentC
  |- |- view
  |  .
  |  .
  |- componentN
  |  |- controller
  |  |- model
  |  |  |- actions
  |  |  |- services
  |  |  |- state
  |  |- view 
  |- store

API (API)

createSubState(subState);
const store = createStore(initialState, combineSelectors(selectors));

const { componentAState } = store.getState();
const { selector1, selector2 } = store.getSelectors();
const [{ componentAState }, { selector1, selector2 }] = store.getStateAndSelectors();

Detailed API documentation

详细的API文档

API范例 (API Examples)

Create initial states

创建初始状态

const initialComponentAState = {
  prop1: 0,
  prop2: 0
};

Create selectors

创建选择器

When using foreign state inside selectors, prefer creating foreign state selectors and accessing foreign state through them instead of directly accessing foreign state inside selector. This will ensure better encapsulation of component state.

在选择器内部使用外部状态时,最好创建外部状态选择器并通过它们访问外部状态,而不是直接在选择器内部访问外部状态。 这将确保更好地封装组件状态。

const createComponentASelectors = <T extends State>() => ({
  selector1: (state: State) => state.componentAState.prop1  + state.componentAState.prop2
  selector2: (state: State) => {
    const { componentBSelector1, componentBSelector2 } = createComponentBSelectors<State>();
    return state.componentAState.prop1 + componentBSelector1(state) + componentBSelector2(state);
  }
});

Create and export store in store.ts:

在store.ts中创建和导出商店:

combineSelectors() checks if there are duplicate keys in selectors and will throw an error telling which key was duplicated. By using combineSelectors you can keep your selector names short and only namespace them if needed.

CombineSelectors()检查选择器中是否存在重复的键,并且将抛出错误,告诉您哪个键被重复。 通过使用CombineSelector,可以使选择器名称简短,并且仅在需要时使用名称空间。

const initialState = {
  componentAState: createSubState(initialComponentAState),
  componentBState: createSubState(initialComponentBState)
};

export type State = typeof initialState;

const componentAStateSelectors = createComponentAStateSelectors<State>();
const componentBStateSelectors = createComponentBStateSelectors<State>();

const selectors = combineSelectors<State, typeof componentAStateSelectors, typeof componentBStateSelectors>(
  componentAStateSelectors,
  componentBStateSelectors
);

export default createStore<State, typeof selectors>(initialState, selectors);

in large projects you should have sub stores for components and these sub store are combined together to a single store in store.js:

在大型项目中,您应该具有用于​​组件的子存储,并且这些子存储在store.js中组合在一起形成单个存储:

componentBStore.js

componentBStore.js

const componentBInitialState = { 
  componentBState: createSubState(initialComponentBState),
  componentB_1State: createSubState(initialComponentB_1State),
  component1ForComponentBState: createSubState(initialComponent1State) 
};

const componentBStateSelectors = createComponentBStateSelectors<State>();
const componentB_1StateSelectors = createComponentB_1StateSelectors<State>();
const component1ForComponentBSelectors = createComponent1Selectors<State>('componentB');

const componentBSelectors = combineSelectors<State, typeof componentBStateSelectors, typeof componentB_1StateSelectors, typeof component1ForComponentBSelectors>(
  componentBStateSelectors,
  componentB_1StateSelectors,
  component1ForComponentBSelectors
);

store.js

store.js

const initialState = {
  ...componentAInitialState,
  ...componentBInitialState,
  .
  ...componentNInitialState
};
      
export type State = typeof initialState;
    
const selectors = combineSelectors<State, typeof componentASelectors, typeof componentBSelectors, ... typeof componentNSelectors>(
  componentASelectors,
  componentBSelectors,
  .
  componentNSelectors
);
    
export default createStore<State, typeof selectors>(initialState, selectors);

Access store in Actions

在动作中访问存储

Don't modify other component's state directly inside action, but instead call other component's action. This will ensure encapsulation of component's own state.

不要直接在动作内部修改其他组件的状态,而要调用其他组件的动作。 这将确保封装组件自身的状态。

export default function changeComponentAAndBState(newAValue, newBValue) {
  const { componentAState } = store.getState();
  componentAState.prop1 = newAValue;
  
  // BAD
  const { componentBState } = store.getState();
  componentBState.prop1 = newBValue;
  
  // GOOD
  changeComponentBState(newBValue);
}

Use actions, state and selectors in View

在视图中使用动作,状态和选择器

Components should use only their own state and access other components' states using selectors provided by those components. This will ensure encapsulation of each component's state.

组件应仅使用其自己的状态,并使用由这些组件提供的选择器访问其他组件的状态。 这将确保封装每个组件的状态。

export default {
  setup(): object {
    const [ { componentAState }, { selector1, selector2 }] = store.getStateAndSelectors();
  
  return {
    componentAState,
    selector1,
    selector2,
    // Action
    changeComponentAState
  };
}

(Example)

视图 (View)

App.vue

应用程序

<template>
  <div>
    <HeaderView />
    <TodoListView />
  </div>
</template>

<script lang="ts">
import HeaderView from '@/header/view/HeaderView.vue';
import TodoListView from '@/todolist/view/TodoListView.vue';

// noinspection JSUnusedGlobalSymbols
export default {
  name: 'App',
  components: { HeaderView, TodoListView }
};
</script>

<style scoped></style>

HeaderView.vue

HeaderView.vue

<template>
  <div>
    <h1>{{ headerText }}</h1>
    <label for="userName">User name:</label>
    <input id="userName" @change="({ target: { value } }) => changeUserName(value)" />
  </div>
</template>

<script lang="ts">
import store from '@/store/store';
import changeUserName from '@/header/model/actions/changeUserName';

export default {
  name: 'HeaderView',

  setup(): object {
    const { headerText } = store.getSelectors();

    return {
      headerText,
      changeUserName
    };
  }
};
</script>

<style scoped></style>

TodoListView.vue

TodoListView.vue

<template>
  <div>
    <input
      id="shouldShowOnlyUnDoneTodos"
      type="checkbox"
      :checked="todosState.shouldShowOnlyUnDoneTodos"
      @click="toggleShouldShowOnlyUnDoneTodos"
    />
    <label for="shouldShowOnlyUnDoneTodos">Show only undone todos</label>
    <div v-if="todosState.isFetchingTodos">Fetching todos...</div>
    <div v-else-if="todosState.hasTodosFetchFailure">Failed to fetch todos</div>
    <ul v-else>
      <li v-for="todo in shownTodos">
        <input :id="todo.name" type="checkbox" :checked="todo.isDone" @click="toggleIsDoneTodo(todo)" />
        <label :for="todo.name">{{ userName }}: {{ todo.name }}</label>
        <button @click="removeTodo(todo)">Remove</button>
      </li>
    </ul>
  </div>
</template>

<script lang="ts">
import { onMounted, onUnmounted } from 'vue';
import store from '@/store/store';
import toggleShouldShowOnlyUnDoneTodos from '@/todolist/model/actions/toggleShouldShowOnlyUnDoneTodos';
import removeTodo from '@/todolist/model/actions/removeTodo';
import toggleIsDoneTodo from '@/todolist/model/actions/toggleIsDoneTodo';
import fetchTodos from '@/todolist/model/actions/fetchTodos';
import todoListController from '@/todolist/controller/todoListController';

export default {
  setup(): object {
    const [{ todosState }, { shownTodos, userName }] = store.getStateAndSelectors();

    onMounted(() => {
      fetchTodos();
      document.addEventListener('keydown', todoListController.handleKeyDown);
    });

    onUnmounted(() => {
      document.removeEventListener('keydown', todoListController.handleKeyDown);
    });

    return {
      todosState,
      shownTodos,
      userName,
      removeTodo,
      toggleShouldShowOnlyUnDoneTodos,
      toggleIsDoneTodo
    };
  }
};
</script>

<style scoped></style>

控制者 (Controller)

todoListController.ts

todoListController.ts

import addTodo from "@/todolist/model/actions/addTodo";
import removeAllTodos from "@/todolist/model/actions/removeAllTodos";

export default {
  handleKeyDown(keyboardEvent: KeyboardEvent): void {
    if (keyboardEvent.code === 'KeyA' && keyboardEvent.ctrlKey) {
      keyboardEvent.stopPropagation();
      keyboardEvent.preventDefault();
      addTodo();
    } else if (keyboardEvent.code === 'KeyR' && keyboardEvent.ctrlKey) {
      keyboardEvent.stopPropagation();
      keyboardEvent.preventDefault();
      removeAllTodos();
    }
  }
};

模型 (Model)

商店 (Store)

store.ts

商店

import { combineSelectors, createStore, createSubState } from 'universal-model-vue';
import initialHeaderState from '@/header/model/state/initialHeaderState';
import initialTodoListState from '@/todolist/model/state/initialTodoListState';
import createTodoListStateSelectors from '@/todolist/model/state/createTodoListStateSelectors';
import createHeaderStateSelectors from '@/header/model/state/createHeaderStateSelectors';

const initialState = {
  headerState: createSubState(initialHeaderState),
  todosState: createSubState(initialTodoListState)
};

export type State = typeof initialState;

const headerStateSelectors =  createHeaderStateSelectors<State>();
const todoListStateSelectors = createTodoListStateSelectors<State>();

const selectors = combineSelectors<State, typeof headerStateSelectors, typeof todoListStateSelectors>(
 headerStateSelectors,
 todoListStateSelectors 
);

export default createStore<State, typeof selectors>(initialState, selectors);

(State)

初始状态 (Initial state)

initialHeaderState.ts

initialHeaderState.ts

export default {
  userName: 'John'
};

initialTodoListState.ts

initialTodoListState.ts

export interface Todo {
  name: string;
  isDone: boolean;
}

export default {
  todos: [] as Todo[],
  shouldShowOnlyUnDoneTodos: false,
  isFetchingTodos: false,
  hasTodosFetchFailure: false
};
状态选择器 (State selectors)

createHeaderStateSelectors.ts

createHeaderStateSelectors.ts

import { State } from '@/store/store';

const createHeaderStateSelectors = <T extends State>() => ({
  userName: (state: T) => state.headerState.userName,
  headerText: (state: T) => {
    const {
      todoCount: selectTodoCount,
      unDoneTodoCount: selectUnDoneTodoCount
    } = createTodoListStateSelectors<T>();
  
    return `${state.headerState.userName} (${selectUnDoneTodoCount(state)}/${selectTodoCount(state)})`;
  }
});

export default createHeaderStateSelectors;

createTodoListStateSelectors.ts

createTodoListStateSelectors.ts

import { State } from '@/store/store';
import { Todo } from '@/todolist/model/state/initialTodoListState';

const createTodoListStateSelectors = <T extends State>() => ({
  shownTodos: (state: T) =>
    state.todosState.todos.filter(
      (todo: Todo) =>
        (state.todosState.shouldShowOnlyUnDoneTodos && !todo.isDone) ||
        !state.todosState.shouldShowOnlyUnDoneTodos
    ),
  todoCount: (state: T) => state.todosState.todos.length,
  unDoneTodoCount: (state: T) => state.todosState.todos.filter((todo: Todo) => !todo.isDone).length
});

export default createTodoListStateSelectors;

服务 (Service)

ITodoService.ts

ITodoService.ts

import { Todo } from '@/todolist/model/state/initialTodoListState';

export interface ITodoService {
  tryFetchTodos(): Promise<Todo[]>;
}

FakeTodoService.ts

FakeTodoService.ts

import { ITodoService } from '@/todolist/model/services/ITodoService';
import { Todo } from '@/todolist/model/state/initialTodoListState';
import Constants from "@/Constants";

export default class FakeTodoService implements ITodoService {
  tryFetchTodos(): Promise<Todo[]> {
      return new Promise<Todo[]>((resolve: (todo: Todo[]) => void, reject: () => void) => {
        setTimeout(() => {
          if (Math.random() < 0.95) {
            resolve([
              { name: 'first todo', isDone: true },
              { name: 'second todo', isDone: false }
            ]);
          } else {
            reject();
          }
        }, Constants.FAKE_SERVICE_LATENCY_IN_MILLIS);
      });
    }
}

todoService.ts

todoService.ts

import FakeTodoService from "@/todolist/model/services/FakeTodoService";

export default new FakeTodoService();

动作 (Actions)

changeUserName.ts

changeUserName.ts

import store from "@/store/store";

export default function changeUserName(newUserName: string): void {
  const { headerState } = store.getState();
  headerState.userName = newUserName;
}

addTodo.ts

addTodo.ts

import store from '@/store/store';

export default function addTodo(): void {
  const { todosState } = store.getState();
  todosState.todos.push({ name: 'new todo', isDone: false });
}

removeTodo.ts

removeTodo.ts

import store from '@/store/store';
import { Todo } from '@/todolist/model/state/initialTodoListState';

export default function removeTodo(todoToRemove: Todo): void {
  const { todosState } = store.getState();
  todosState.todos = todosState.todos.filter((todo: Todo) => todo !== todoToRemove);
}

removeAllTodos.ts

removeAllTodos.ts

import store from '@/store/store';

export default function removeAllTodos(): void {
  const { todosState } = store.getState();
  todosState.todos = [];
}

toggleIsDoneTodo.ts

toggleIsDoneTodo.ts

import { Todo } from '@/todolist/model/state/initialTodoListState';

export default function toggleIsDoneTodo(todo: Todo): void {
  todo.isDone = !todo.isDone;
}

toggleShouldShowOnlyUnDoneTodos.ts

toggleShouldShowOnlyUnDoneTodos.ts

import store from '@/store/store';

export default function toggleShouldShowOnlyUnDoneTodos(): void {
  const [{ todosState }] = store.getStateAndSelectors();
  todosState.shouldShowOnlyUnDoneTodos = !todosState.shouldShowOnlyUnDoneTodos;
}

fetchTodos.ts

fetchTodos.ts

import store from '@/store/store';
import todoService from '@/todolist/model/services/todoService';

export default async function fetchTodos(): Promise<void> {
  const { todosState } = store.getState();

  todosState.isFetchingTodos = true;
  todosState.hasTodosFetchFailure = false;
    
  try {
    todosState.todos = await todoService.tryFetchTodos();
  } catch (error) {
    todosState.hasTodosFetchFailure = true;
  }
    
  todosState.isFetchingTodos = false;
}

完整的例子 (Full Examples)

https://github.com/universal-model/universal-model-vue-todo-app

https://github.com/universal-model/universal-model-vue-todo-app

https://github.com/universal-model/universal-model-react-todos-and-notes-app

https://github.com/universal-model/universal-model-react-todos-and-notes-app

依赖注入 (Dependency injection)

If you would like to use dependency injection (noicejs) in your app, check out this [example], where DI is used to create services.

如果您想在应用程序中使用依赖注入(noicejs),请查看此[示例],其中DI用于创建服务。

翻译自: https://vuejsexamples.com/universal-model-for-vue/

vue export 模型

;