예시 프로그램: React, Overmind, 로컬 저장소가 있는 깨끗한 구조, pt.1

면책 성명: 이 글은 겨울 전 도토리밭의 다람쥐처럼 깡충깡충 뛰는 것처럼 보일 수도 있다.
TL;박사: GitHub repository.
그래서 깨끗한 건물!나는 그것의 확고한 지지자이다.이것은 프로젝트의 테스트 가능성을 확보하는 아주 간단한 방법으로 원숭이 한 마리가 할 수 있다.무엇이 청결한 건축물입니까?이를 통해 우리는 의존 주입applicationContext을 사용하여 명확한 경계를 정의함으로써 업무 논리와 기술 창고를 완전히 분리할 수 있다.

나는 상세한 토론을 하지 않을 것이다. 왜냐하면 깨끗한 건축은 다른 사람이 설명하는 것이 가장 좋은 원칙이기 때문이다.예를 들어 이것summary on Gist.누가 이 개념을 창립했습니까?밥 마틴 아저씨.너는 그를 좀 볼 수 있어!

어째서.
우리는 어떻게 이런 분리를 실시할 것인가?Monorepo에서 Lerna를 통해 모든 기능을 구현합니다.나는 원래Terraform을 이용하여 이 기능을 실현하려고 하였으나, 이것은 공사 설계 (예를 들어 예시 응용 프로그램) 와 분리될 수 없다고 생각한다.그렇게 지도 모른다, 아마, 아마...

구조
포장은 어때요?파일 구조는?우선, 보기 ui 가 필요합니다. 이것은 제가 사용한 create-react-app 와 제가 만든 사용자 정의 template 의 앞부분입니다.
그 다음으로 우리는 업무 논리를 놓을 곳이 필요하다business.이것은 우리의 실체, 용례 등을 보존할 것이다.셋째, 우리는 보관 방법이 필요한 곳persistence이 필요하다.이것이 바로 로컬 저장 방법의 발전 방향이다.
지금까지 우리의 구조는 다음과 같다.
  • packages/ui
  • packages/business
  • packages/persistence

  • 풍경.
    들어가자.그래서 나는 create-react-app 템플릿이 있다고 말했다.이것은 기본적으로 내가 만든 청결 구조에 사용되는 전단 샘플이다. 전단과 로컬 저장소에만 사용된다.TypeScript 애호가들에게 본문 이후 조만간 하나 만들겠습니다.이 템플릿은 지속적인 로컬 저장소를 포함한 모든 내용을 앞쪽에 포장합니다.그러나 나는 이 문장을 위해 약간의 조정을 했다.

    주재하다
    나는 (https://overmindjs.org/)[Overmind] 를 사용하여 상태 관리를 한다.그것은 더욱 성명적인 상태 관리 시스템으로 사용자가 마음대로 복잡한 조작을 할 수 있도록 한다.그것의 주요 목적은 개발자가 자신의 응용 프로그램의 테스트 가능성과 읽기 가능성에 전념하도록 하는 것이다.
    나도 통어에 관한 문장을 한 편 쓸 것이다.😁

    비밀 번호
    자, 우리 이제 정말 잠수해야 돼.약속할게.
    우선, 우리는 간단한oleindex.js가 있는데 이것은 사용자 인터페이스를 더욱 뚜렷하게 할 수 있다.
    import React from 'react';
    import ReactDOM from 'react-dom';
    import './index.css';
    import { createOvermind } from 'overmind';
    import { Provider } from 'overmind-react';
    import { config } from './presenter/presenter';
    import App from './App.jsx';
    
    const overmind = createOvermind(config);
    
    ReactDOM.render(
      <Provider value={overmind}>
        <App />
      </Provider>,
      document.getElementById('root'),
    );
    
    이것은 매우 쉽다.나는 App.jsx를 발표하지 않지만 Todosviews/Todos.jsx 구성 요소만 참조합니다.
    import * as React from 'react';
    import { useActions, useState } from '../presenter/presenter';
    
    const Todo = ({ todo }) => {
      useState();
      return (
        <li>
          {todo.title} {todo.description}
        </li>
      );
    };
    
    export const Todos = () => {
      const state = useState();
      const {
        addTodoItemAction,
        updateTodoTitleAction,
        updateTodoDescriptionAction,
      } = useActions();
    
      return (
        <>
          <input
            type="text"
            name="title"
            placeholder="Title"
            onChange={e => updateTodoTitleAction(e.target.value)}
          />
          <input
            type="textarea"
            name="description"
            placeholder="Description"
            onChange={e => updateTodoDescriptionAction(e.target.value)}
          />
          <button onClick={addTodoItemAction}>Add</button>
          <ul>
            {state.todos.map(todo => (
              <Todo key={todo.id} todo={todo} />
            ))}
          </ul>
        </>
      );
    };
    
    우리는 Overmind 분야에 깊이 들어가고 있기 때문에, 나는 여기서 몇 가지 일을 설명할 것이다. 우리는 두 개의 갈고리useActionsuseState가 있는데, 이 두 갈고리는 우리의 현재 응용 프로그램 상태와 Overmindactions에 끌어들였다.동작은 본질적으로 상태 읽기와 돌연변이가 발생하는 곳이자 우리가 applicationContext 정보를 주입하는 곳이다.나는 Overmind가 있는 디렉터리를 presenter 라고 명명했다. 왜냐하면 이것이 우리의 논리가 있는 디렉터리이기 때문이다.
    이 파일을 보십시오ui/presenter/presenter.js:
    import {
      createStateHook,
      createActionsHook,
      createEffectsHook,
      createReactionHook,
    } from "overmind-react";
    import { state } from "./state";
    import { applicationContext } from '../applicationContext';
    import { addTodoItemAction } from './actions/addTodoItemAction';
    import { updateTodoTitleAction } from './actions/updateTodoTitleAction';
    import { updateTodoDescriptionAction } from './actions/updateTodoDescriptionAction';
    import { deleteTodoItemAction } from './actions/deleteTodoItemAction';
    
    const actions = {
      addTodoItemAction,
      updateTodoTitleAction,
      updateTodoDescriptionAction,
      deleteTodoItemAction,
    };
    
    export const config = {
      state,
      actions,
      effects: applicationContext,
    };
    
    export const useState = createStateHook();
    export const useActions = createActionsHook();
    export const useEffects = createEffectsHook();
    export const useReaction = createReactionHook();
    
    자세히 연구한 후에 applicationContext의 동작이 어떤지 보고 싶을 것이다.제가 여러분께 보여드리기 전에applicationContext:
    export const addTodoItemAction = ({ state, effects: { ...applicationContext }}) => {
      const { todoTitle: title, todoDescription: description } = state;
    
      const todos = applicationContext.getUseCases().addTodoItemInteractor({
        applicationContext,
        title,
        description,
      });
    
      state.todos = todos;
    }
    
    아주 간단해. (곤혹스러워하는 사람들에게는 더 간단해질 거라고 장담해.) 정말이야.우리는 presenter/actions/addTodoItemAction.js에서 우리의 용례를 얻었다.당신은 왜 상호작용자를 포함하지 않습니까? 왜 이렇게 합니까? 그래요. 단원 테스트를 봅시다.
    const { createOvermindMock } = require("overmind");
    const { config } = require("../presenter");
    
    describe("addTodoItemAction", () => {
      let overmind;
      let addTodoItemInteractorStub;
      let mockTodo = { title: "TODO Title", description: "TODO Description" };
    
      beforeEach(() => {
        addTodoItemInteractorStub = jest.fn().mockReturnValue([mockTodo]);
    
        // TODO: refactor
        overmind = createOvermindMock(
          {
            ...config,
            state: { todoTitle: "TODO Title", todoDescription: "TODO Description" },
          },
          {
            getUseCases: () => ({
              addTodoItemInteractor: addTodoItemInteractorStub,
            }),
          }
        );
      });
    
      it("calls the interactor to add a todo item", async () => {
        await overmind.actions.addTodoItemAction();
    
        expect(addTodoItemInteractorStub).toHaveBeenCalled();
        expect(addTodoItemInteractorStub).toHaveBeenCalledWith({
          applicationContext: expect.anything(),
          ...mockTodo,
        });
        expect(overmind.state).toEqual(
          expect.objectContaining({
            todos: [mockTodo],
          })
        );
      });
    });
    
    나는 매번 테스트에서 사용하는 것이 아니라 시뮬레이션applicationContext을 원한다applicationContext.단원 테스트는 잠재적인 대형 코드 라이브러리에 상하문을 공유할 수 있으며, 이것은 우리가 이 테스트를 작성하는 데 많은 시간을 절약할 수 있다.나는 그것이 더 좋은 또 다른 이유는 테스트 구동 개발을 통해 논리를 설계/정의하는 것이라고 생각한다.

    장사
    알겠습니다. 우리는 이미 용례 또는 jest.mock 라고 불리는 조작을 소개했습니다.먼저 위에서 설명한 상호 작용 프로그램interactors을 살펴보고 비즈니스 논리에 대해 자세히 살펴보겠습니다.
    import { Todo } from '../entities/Todo';
    
    /**
     * use-case for adding a todo item to persistence
     *
     * @param {object} provider provider object
     */
    export const addTodoItemInteractor = ({ applicationContext, title, description }) => {
      const todo = new Todo({ title, description }).validate().toRawObject();
    
      const todos = [];
      const currentTodos = applicationContext.getPersistence().getItem({
        key: 'todos',
        defaultValue: [],
      });
    
      if (currentTodos) {
        todos.push(...currentTodos);
      }
    
      todos.push(todo);
    
      applicationContext.getPersistence().setItem({ key: 'todos', value: todos });
    
      return todos;
    };
    
    우리가 뭘 해야 하는지 알아?이 상호작용기는 위의 그림에서 실체packages/business/useCases/addTodoItemInteractor.js 주위의 용례이다.그것은 두 가지 Todo 방법을 호출했는데, 기본적으로 내가 만든 로컬 메모리 패키지이다.이 상호작용자의 단원 테스트를 살펴보자.
    const { addTodoItemInteractor } = require("./addTodoItemInteractor");
    
    describe("addTodoItemInteractor", () => {
      let applicationContext;
      let getItemStub;
      let setItemStub;
    
      beforeAll(() => {
        getItemStub = jest.fn().mockReturnValue([]);
        setItemStub = jest.fn();
    
        applicationContext = {
          getPersistence: () => ({
            getItem: getItemStub,
            setItem: setItemStub,
          }),
        };
      });
    
      it("add a todo item into persistence", () => {
        const result = addTodoItemInteractor({
          applicationContext,
          title: "TODO Title",
          description: "TODO Description",
        });
    
        expect(getItemStub).toHaveBeenCalled();
        expect(getItemStub).toHaveBeenCalledWith({
          key: "todos",
          defaultValue: [],
        });
        expect(setItemStub).toHaveBeenCalled();
        expect(setItemStub).toHaveBeenCalledWith({
          key: "todos",
          value: [
            {
              title: "TODO Title",
              description: "TODO Description",
            },
          ],
        });
        expect(result).toEqual([
          {
            title: "TODO Title",
            description: "TODO Description",
          },
        ]);
      });
    });
    
    가볍다모든 것은 캐시, 아날로그 등으로 저장될 수 있습니다. 우리가 관심 있는 것은 로컬 저장소의 내용이 아니라, 로컬 저장소든 원격/로컬 데이터베이스든, UI나 Overmind 논리든 상관없습니다.
    우리는 업무 논리에만 관심을 갖는다.이것이 바로 우리가 이곳에서 테스트한 전부이며, 우리가 이곳에서 테스트한 전부이다.이러한 지속화 방법persistencesetItem을 살펴보자.

    꾸준하다
    상술한 두 가지 방법은 각각 getItemsetItem이다.아주 단도직입적이다.솔직히 말하면, 나는 그것들을 포장할 필요가 없을 것 같다.그러나 지속성이 쉽게 바뀔 수 있도록 우리가 무엇을 사용하든지 실제 상호작용 내부에서 바꿀 필요가 없다는 것을 보여주고 싶다.
    어디 보자getItem:
    module.exports.setItem = ({ key, value }) =>
      localStorage.setItem(key, JSON.stringify(value));
    
    간단해.유닛 테스트:
    const { setItem } = require('./setItem');
    
    describe('setItem', () => {
      let setItemStub;
      global.localStorage = {};
    
      beforeEach(() => {
        setItemStub = jest.fn();
    
        global.localStorage.setItem = setItemStub;
      });
    
      it('sets the item given the key/value pair', () => {
        setItem({ key: 'todos', value: 'todos value' });
    
        expect(setItemStub).toHaveBeenCalled();
        expect(setItemStub).toHaveBeenCalledWith('todos', JSON.stringify('todos value'));
      });
    });
    
    아주 간단하죠?단원 테스트는 하나의 모델이 있는데, 나는 사람들이 샘플 파일을 줄일 수 있는 방법을 찾을 수 있을 것이라고 믿는다.혹은 하나의 매크로를 만들 뿐이다. 왜냐하면 대부분의 중복된 내용은 각자의 단원 테스트에 없어서는 안 되기 때문이다.
    주의: 우리가 JSON을 문자열화하는 유일한 이유는 저장 대상/그룹을 허용하기 때문입니다. (작업 중 setItem 는 하나의 그룹입니다.)
    이것은 분명히 전부가 아니다.나는 세부 사항을 깊이 연구하고 싶지 않다.다음 글은 백엔드 (서버가 없을 수도 있음) 로 같은 설정을 연결하는 것을 포함할 것입니다.우리는 어떤 데이터베이스를 사용해야 합니까?DynamoDB인가요, 아니면 PostgreSQL과 같은 관계 데이터베이스인가요?그렇게 지도 모른다, 아마, 아마...
    읽어주셔서 감사합니다!만약 내가 무엇을 잘못 걸었거나, 당신에게 어떤 문제, 평론, 걱정, 건의가 있다면, 평론에 발표하세요.안녕히 계세요.

    좋은 웹페이지 즐겨찾기