Une autre approche de l'architecture de construction à l'avant

Bonjour, chers lecteurs. Dans cet article, je vais essayer de parler du principe de construction d'une architecture pour le frontend, en particulier pour React, car une bonne architecture doit être un élément indépendant du système.





Dans le cadre de l'article, j'essaierai simplement de considérer et d'apporter des réponses aux sujets suivants:





  • qu'est-ce que l'architecture et pourquoi devrait-elle être propre;





  • comment écrire une architecture basée sur des services;





  • un exemple de construction d'une architecture pour une application de notes;





  • .






, . . , , , .





- , - .





- , , , . /, , -, , , ..





, , . , . .





- , . , , , .





OSAL (Operating System Abstraction Layer) - , HAL (Hardware Abstraction Layer), , . .





BSAL (Browser System Abstraction Layer). , , .





, , NodeJS.





. . , , .





, , - .





, , - . , - ( ), .





:





import { createBrowserHistory } from 'history';

class HistoryAPI {
    protected history = createBrowserHistory({});

    push(pathname: string): void {
        this.history.push(pathname);
    }
}

export default HistoryAPI;

      
      



, , , - . - . , .





- , , - . , .





- . , . , .





, . new



. , . , , .





:





const netAPI = new NetAPI();

const newNoteRepository = new NetNoteRepository(netAPI);

const noteService = new NoteService(newNoteRepository);
      
      



"", , , :





  1. - , . , JSON, , - ;





  2. - , , , ;





  3. ( ) - , , , , .. ;





  4. - , . . , , ..;





  5. - , , ;





  6. - "" , .





. , .





, , . , , .





, . .





, UML .





, JSON. . .





:





interface Note {
    id: string;
    name: string;
    description: string;
    created: number;
    tags: string[];
}

interface Filter {
    including: string;
    tags: string[];
}

interface User {
    name: string;
    role: 'user' | 'admin';
}
      
      



- , .





:





interface Factory {
    makeNote(): Note;
    makeFilter(): Filter;
}
      
      



, , : , - .





:





interface NoteHandler {
    note: Note;
    setName(name: string): void;
    setDescription(description: string): void;
    addTag(tag: string): void;
    removeTag(tag: string): void;
    validate(): Errors<Note>;
}
      
      



.





, API.





API

API , , , , ..





:





interface NetAPI {
    get<T>(url: string): Promise<T>;
    post<T, D>(url: string, data: D): Promise<T>;
}

      
      



API .





, axios, fetch , NetAPI



.





- , . , .





:





interface NoteRepository {
    loadNotes(filter: Filter): Promise<Note[]>;
    save(note: Note): Promise<boolean>;
}
      
      



, . , MockedNoteRepository



.





, , , .





,

, . , , - .





, , , .





:





interface Emmitable<E> {
    on<K extends keyof E>(event: K, cb: (event: E[K]) => void): void;
    off<K extends keyof E>(event: K, cb: (event: E[K]) => void): void;
    emit<K extends keyof E>(event: K, data: E[K]): void;
}

interface NoteEvents {
    change: undefined;
    notesChange: Note[];
    filterChange: Filter;
}

class NoteService implements Emmitable<NoteEvents> {
    noteRepository: NoteRepository;

    notes: Note[] = [];

    filter: Filter = {
        including: '',
        tags: [],
    }

    private callbacks: {
        [K in keyof NoteEvents]?: ((event: NoteEvents[K]) => void)[];
    } = {};

    
    on<K extends keyof NoteEvents>(event: K, cb: (event: NoteEvents[K]) => void): void {
        if (!this.callbacks[event]) {
            this.callbacks[event] = [];
        }
        const callbacks = this.callbacks[event];
        if (!Array.isArray(callbacks)) {
            return;
        }
        callbacks.push(cb);
    }

    off<K extends keyof NoteEvents>(event: K, cb: (event: NoteEvents[K]) => void): void {
        if (!this.callbacks[event]) {
            return;
        }
        const callbacks = this.callbacks[event];
        if (!Array.isArray(callbacks)) {
            return;
        }
        const index = callbacks.findIndex((aCallback) => aCallback === cb);
        if (index !== -1) {
            callbacks.splice(index, 1);
        }
    }

    emit<K extends keyof NoteEvents>(event: K, data: NoteEvents[K]): void {
        setTimeout(() => {
            if (!this.callbacks[event]) {
                return;
            }
            const callbacks = this.callbacks[event];
            if (!Array.isArray(callbacks)) {
                return;
            }
            callbacks.forEach((callback) => {
                callback(data);
            });
        }, 0);
    }

    constructor(noteRepository: NoteRepository) {
        this.noteRepository = noteRepository;
    }

    loadNotes(): Promise<boolean> {
        return this.noteRepository
            .loadNotes(this.filter)
            .then((notes) => {
                this.notes = notes;
                this.emit('notesChange', this.notes);
                this.emit('change', undefined);
                return true;
            });
    }

    saveNote(note: Note): Promise<boolean> {
        return this.noteRepository
            .save(note)
            .then(() => this.loadNotes());
    }

    setFilter(filter: Filter): void {
        this.filter = filter;
        this.emit('filterChange', this.filter);
        this.loadNotes();
    }
}

      
      



Emmitable<E>



, , .





, , - , . change



. , filterChange



.





- , - .





, MobX , , .





. , .





:





  1. , - ;





  2. - , .





:





-

- , - index.ts



. , API, . new



. , . Lego, .





:





const netAPI = new NetAPI();
const tokenGetter = new TokenGetter(netAPI);
const authNetAPI = new AuthNetAPI(tokenGetter);
const historyAPI = new HistoryAPI();

const services: Services = {
    note: new NoteService(new NoteRepository(authNetAPI)),
    modal: new ModalService(),
    page: new PageService(historyAPI),
    auth: new AuthService(new AuthRepository(netAPI)),
    user: new UserService(new UserRepository(authNetAPI)),
};

const application = new Application(services, tokenGetter, authNetRequest);
      
      



, .





, , .





:





function saveNote(services: Services, note: Note): void {
    services.note.saveNote(note)
        .then(() => {
            services.modal.setModal({
                type: 'success',
                title: '  ',
                description: '',
                onClose: () => {
                    services.modal.setModal(undefined);
                },
            });
            services.page.setPage({
                type: 'notes',
            });
        })
        .catch(() => {
            services.modal.setModal({
                type: 'error',
                title: '   ',
                description: '',
                onClose: () => {
                    services.modal.setModal(undefined);
                },
            });
        })
}
      
      



, . . :





class Scenarios {
    private services: Services;

    constructor(services: Services) {
        this.services = services;
    }

    saveNote(note: Note): void {
        // ...
    }
}
      
      



.





, - , index.ts



:





const root = document.getElementById('root');

ReactDOM.render(<App services={services} />, root);
      
      



, :





export default React.createContext<Services>({} as Services);
      
      



:





interface AppProps {
    services: Services;
}

const App: FC<AppProps> = ({ services }) => {
    return (
        <ServiceContext.Provider value={services}>
            {<AppContainer />}
        </ServiceContext.Provider>
    );
};
      
      



:





export default function useService<K extends keyof Services>(service: K): Services[K] {
    const services = useContext(ServiceContext);
    return services[service];
}
      
      



:





const NotesPage: FC = () => {
    const noteService = useService('note');

    const [notes, setNotes] = useState<Note[]>(noteService.notes);

    useEffect(() => {
        const onChange = () => {
            setNotes(noteService.notes.concat());
        };

        noteService.on('change', onChange);

        return () => {
            noteService.off('change', onChange);
        };
    }, [noteService]);
    // ...
}
      
      



, :





  • - , , ;





  • - , , .





, .





, , .





, . , .





, , . - .





, , : , . , , .





L'exemple considéré de création d'une application ne montre que des recommandations et une approche pour construire votre architecture. Par conséquent, votre architecture doit être la vôtre et dépendre de la signification même de l'application.








All Articles