Il me semble que le moment est venu de partager l'approche de l'écriture de l'App ReactJS,
je ne prétends pas être unique.Le premier paragraphe peut être ignoré . Je suis engagé dans le développement Web depuis longtemps, mais depuis quatre ans, je suis assis sur ReactJS et tout me convient, j'ai eu redux dans ma vie, mais il y a environ deux ans, j'ai rencontré MobX, il y a seulement quelques mois, j'ai essayé de revenir à redux, mais je ne l'ai pas fait Je pouvais, j'avais le sentiment que je faisais quelque chose de superflu, peut-être que quelque chose n'allait pas du tout, beaucoup d'octets sur les serveurs avaient déjà été traduits sur ce sujet, l'article ne parlait pas de la fraîcheur de l'un avant l'autre, c'est juste une tentative de partager mes meilleures pratiques, peut-être quelqu'un vraiment cette approche ira, et donc au point.
Les tâches que nous allons résoudre:- di connexion pour composants
- rendu serveur avec chargement asynchrone des données
La structure du projet peut être consultée sur
Github . Par conséquent, je vais sauter comment écrire une application primitive et l'article ne mettra en évidence
Nous introduisons des concepts tels que: modèle de données, service, côté.
Prenons un modèle simple
TodoModel.tsimport { observable, action } from 'mobx'; export class TodoModel { @observable public id: number; @observable public text: string = ''; @observable public isCompleted: boolean = false; @action public set = (key: 'text' | 'isCompleted', value: any): void => { this[key] = value; }; }
ce que vous voyez est l'action définie, dans le modèle c'est plus une exception qu'un bon ton, généralement dans un projet il y a un modèle de base avec des assistants primitifs et il est juste hérité de celui-ci, dans les modèles il ne devrait y avoir aucune action pour de bon.
Maintenant, nous devons apprendre à travailler avec ce modèle, démarrer un service:
TodoService.ts import { Service, Inject } from 'typedi'; import { plainToClass, classToClass } from 'class-transformer'; import { DataStorage } from '../storage/DataStorage'; import { action } from 'mobx'; import { TodoModel } from '../models/TodoModel'; const responseMock = { items: [ { id: 1, isCompleted: false, text: 'Item 1' }, { id: 2, isCompleted: true, text: 'Item 2' } ] }; @Service('TodoService') export class TodoService { @Inject('DataStorage') public dataStorage: DataStorage; @action public load = async () => { await new Promise(resolve => setTimeout(resolve, 300)); this.dataStorage.todos = plainToClass(TodoModel, responseMock.items); }; @action public save(todo: TodoModel): void { if (todo.id) { const idx = this.dataStorage.todos.findIndex(item => todo.id === item.id); this.dataStorage.todos[idx] = classToClass(todo); } else { const todos = this.dataStorage.todos.slice(); todo.id = Math.floor(Math.random() * Math.floor(100000)); todos.push(todo); this.dataStorage.todos = todos; } this.clearTodo(); } @action public edit(todo: TodoModel): void { this.dataStorage.todo = classToClass(todo); } @action public clearTodo(): void { this.dataStorage.todo = new TodoModel(); } }
Notre service a un lien vers
DataStorage.ts import { Service } from 'typedi'; import { observable } from 'mobx'; import { TodoModel } from '../models/TodoModel'; @Service('DataStorage') export class DataStorage { @observable public todos: TodoModel[] = []; @observable public todo: TodoModel = new TodoModel(); }
Dans ce magasin, nous enregistrerons l'état de notre application, il peut y en avoir beaucoup, mais comme la pratique l'a montré, cela n'a aucun sens de pénétrer dans de nombreux petits magasins. Dans le magasin, ainsi que dans les modèles, il ne devrait y avoir aucune action.
Nous avons presque tout prêt, il reste à tout connecter à notre application, pour cela nous resserrerons légèrement l'injecteur de mobx-react:
DI import { inject } from 'mobx-react'; export function DI(...classNames: string[]) { return (target: any) => { return inject((props: any) => { const data: any = {}; classNames.forEach(className => { const name = className.charAt(0).toLowerCase() + className.slice(1); data[name] = props.container.get(className); }); data.container = props.container; return data; })(target); }; }
et obtenez un conteneur pour notre DI
browser.tsx import 'reflect-metadata'; import * as React from 'react'; import { hydrate } from 'react-dom'; import { renderRoutes } from 'react-router-config'; import { Provider } from 'mobx-react'; import { BrowserRouter } from 'react-router-dom'; import { Container } from 'typedi'; import '../application'; import { routes } from '../application/route'; hydrate( <Provider container={Container}> <BrowserRouter>{renderRoutes(routes)}</BrowserRouter> </Provider>, document.getElementById('root') );
Pour le navigateur, nous avons toujours un conteneur, mais pour le rendu du serveur que vous devez rechercher, il est préférable d'organiser votre conteneur pour chaque demande:
server.tsx import * as express from 'express'; import * as React from 'react'; import { Container } from 'typedi'; import '../application';
Le rendu du serveur est en fait une chose délicate, d'une part, je veux tout laisser passer,
mais il n'a qu'une seule tâche commerciale, donner le contenu aux bots , il est donc préférable de vérifier quelque chose comme "l'utilisateur s'est-il connecté au moins une fois sur le site" et ignorez le rendu du serveur en créant des conteneurs sur le serveur.
Eh bien, maintenant à nos composants:
MainRoute.tsx import * as React from 'react'; import { TodoService } from '../service/TodoService'; import { observer } from 'mobx-react'; import { DI } from '../annotation/DI'; import { DataStorage } from '../storage/DataStorage'; import { Todo } from '../component/todo'; import { Form } from '../component/form/Form'; import { ContainerInstance } from 'typedi'; interface IProps { todoService?: TodoService; dataStorage?: DataStorage; } @DI('TodoService', 'DataStorage') @observer export class MainRoute extends React.Component<IProps> { public static async loadData(container: ContainerInstance) { const todoService: TodoService = container.get('TodoService'); await todoService.load(); } public componentDidMount() { this.props.todoService.load(); } public render() { return ( <div> <Form /> <ul> {this.props.dataStorage.items.map(item => ( <li key={item.id} ><Todo model={item} /></li> ))} </ul> </div> ); } }
Tout se révèle très logique et beau ici, notre vue de «rendu» pour le dessin prend les données de notre magasin, les crochets de composants indiquent à quel moment nous devons charger les données.
Todo.tsx import * as React from 'react'; import { TodoModel } from '../../models/TodoModel'; import { TodoService } from '../../service/TodoService'; import { DI } from '../../annotation/DI'; import { observer } from 'mobx-react'; interface IProps { model: TodoModel; todoService?: TodoService; } @DI('TodoService') @observer export class Todo extends React.Component<IProps> { public render() { const { model, todoService } = this.props; return ( <> <input type='checkbox' checked={model.isCompleted} onChange={e => model.set('isCompleted', e.target.checked)} /> <h4>{model.text}</h4> <button type='button' onClick={() => todoService.edit(model)}>Edit</button> </> ); } }
Form.tsx import * as React from 'react'; import { observer } from 'mobx-react'; import { DI } from '../../annotation/DI'; import { TodoService } from '../../service'; import { DataStorage } from '../../storage'; import { TextField } from '../text-field'; interface IProps { todoService?: TodoService; dataStorage?: DataStorage; } @DI('TodoService', 'DataStorage') @observer export class Form extends React.Component<IProps> { public handleSave = (e: any) => { e.preventDefault(); this.props.todoService.save(this.props.dataStorage.todo); }; public handleClear = () => { this.props.todoService.clearTodo(); }; public render() { const { dataStorage } = this.props; return ( <form onSubmit={this.handleSave}> <TextField name='text' model={dataStorage.todo} /> <button>{dataStorage.todo.id ? 'Save' : 'Create'}</button> <button type='button' onClick={this.handleClear}> Clear </button> </form> ); } }
À mon avis, travailler avec des formulaires est beaucoup plus pratique grâce aux modèles / dtos, vous pouvez utiliser les formulaires natifs habituels et mettre à jour le modèle de données et tous ceux qui l'écoutent seront mis à jour instantanément.
Quelque chose comme ça j'utilise ce tas de bibliothèques: react, class-transformer, mobx, typedi
Nous utilisons maintenant cette approche dans prod. Ce sont de très grands projets avec des composants et des services communs communs.
Si cette approche est intéressante, je vais vous expliquer comment, dans la même veine, nous validons les modèles avant de les envoyer au serveur, comment nous traitons les erreurs du serveur et comment nous synchronisons notre état entre les onglets du navigateur.
En fait, tout est très bonus: "class-validator", "localStorage + window.addEventListener ('storage')"
Merci d'avoir lu :-)
Exemple