Sepertinya saya sudah tiba saatnya untuk berbagi pendekatan untuk menulis Aplikasi ReactJS,
saya tidak mengklaim sebagai unik.Paragraf pertama bisa dilewati . Saya telah terlibat dalam pengembangan web untuk waktu yang lama, tetapi selama empat tahun terakhir saya telah duduk ketat di ReactJS dan semuanya cocok untuk saya, saya memiliki redux dalam hidup saya, tetapi sekitar dua tahun lalu saya bertemu MobX, hanya beberapa bulan yang lalu saya mencoba untuk kembali ke redux, tetapi saya tidak melakukannya. Saya bisa, ada perasaan bahwa saya sedang melakukan sesuatu yang berlebihan, mungkin ada sesuatu yang tidak benar sama sekali, banyak byte pada server sudah diterjemahkan pada topik ini, artikel itu bukan tentang kesejukan satu sebelum yang lain, ini hanya upaya untuk berbagi praktik terbaik saya, mungkin seseorang benar-benar pendekatan ini akan berjalan, dan to the point.
Tugas-tugas yang akan kita selesaikan:- koneksi untuk komponen
- rendering server dengan pemuatan data yang tidak sinkron
Struktur proyek dapat dilihat di
Github . Oleh karena itu, saya akan melewatkan cara menulis aplikasi primitif dan artikel hanya akan menyoroti
Kami memperkenalkan konsep-konsep seperti: model data, layanan, sisi.
Mari kita dapatkan model yang sederhana
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; }; }
apa yang Anda lihat adalah tindakan yang ditetapkan, dalam model itu lebih merupakan pengecualian daripada nada yang baik, biasanya dalam proyek ada model dasar dengan pembantu primitif dan itu hanya diwarisi darinya, dalam model tidak boleh ada tindakan untuk kebaikan.
Sekarang kita perlu belajar cara bekerja dengan model ini, memulai layanan:
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(); } }
Layanan kami memiliki tautan ke
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(); }
Di toko ini kami akan menyimpan status aplikasi kami, mungkin ada banyak toko seperti itu, tetapi seperti yang telah ditunjukkan, tidak masuk akal untuk masuk ke banyak toko kecil. Di toko, serta di model, seharusnya tidak ada tindakan.
Kami sudah hampir semuanya siap, masih untuk menghubungkan semuanya ke aplikasi kami, untuk ini kami akan sedikit mengencangkan injektor dari 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); }; }
dan dapatkan wadah untuk DI kami
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') );
Untuk browser, kami selalu memiliki satu wadah, tetapi untuk server membuat Anda perlu melihatnya, lebih baik mengatur wadah Anda untuk setiap permintaan:
server.tsx import * as express from 'express'; import * as React from 'react'; import { Container } from 'typedi'; import '../application';
Server render sebenarnya adalah hal yang rumit, di satu sisi saya ingin membiarkan semuanya melewatinya,
tetapi hanya memiliki satu tugas bisnis, untuk memberikan konten ke bot , jadi lebih baik untuk memeriksa sesuatu seperti "apakah pengguna login setidaknya sekali di situs" , dan lewati server render dengan membuat kontainer di server.
Nah, sekarang ke komponen kami:
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> ); } }
Semuanya ternyata sangat logis dan indah di sini, tampilan "render" kami untuk menggambar mengambil data dari toko kami, kait komponen mengatakan pada titik waktu kapan kami harus memuat data.
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> ); } }
Menurut pendapat saya, bekerja dengan formulir jauh lebih nyaman melalui model / dtos, Anda dapat menggunakan formulir asli biasa, dan memperbarui model data dan semua orang yang mendengarkannya akan diperbarui secara instan.
Sesuatu seperti ini saya menggunakan banyak perpustakaan ini: react, class-transformer, mobx, typedi
Kami sekarang menggunakan pendekatan ini dalam prod. Ini adalah proyek yang sangat besar dengan komponen dan layanan umum yang sama.
Jika pendekatan ini menarik, saya akan memberi tahu Anda bagaimana, dengan nada yang sama, kami melakukan validasi model sebelum mengirimkannya ke server, bagaimana kami memproses kesalahan server dan bagaimana kami menyinkronkan keadaan kami di antara tab browser.
Faktanya, semuanya sangat bonus: "class-validator", "localStorage + window.addEventListener ('storage')"
Terima kasih telah membaca :-)
Contoh