มีบทความมากมายที่ถกเถียงกันว่า React หรือ Angular เป็นตัวเลือกที่ดีกว่าสำหรับการพัฒนาเว็บ เรายังต้องการอีกหรือไม่?
เหตุผลที่ฉันเขียนบทความนี้เป็นเพราะไม่มี ที่ บทความ เผยแพร่แล้ว แล้วแม้ว่าจะมีข้อมูลเชิงลึกที่ยอดเยี่ยม แต่ก็เจาะลึกเพียงพอสำหรับนักพัฒนาส่วนหน้าที่ใช้งานได้จริงในการตัดสินใจว่าจะเลือกตัวใดที่เหมาะกับความต้องการของตน
ในบทความนี้คุณจะได้เรียนรู้ว่าทั้ง Angular และ React มีจุดมุ่งหมายเพื่อแก้ปัญหาส่วนหน้าที่คล้ายกันอย่างไรแม้ว่าจะมีปรัชญาที่แตกต่างกันมากก็ตามและการเลือกอย่างใดอย่างหนึ่งเป็นเพียงเรื่องของความชอบส่วนบุคคลเท่านั้น ในการเปรียบเทียบเราจะสร้างแอปพลิเคชั่นเดียวกันสองครั้งโดยใช้ Angular จากนั้นอีกครั้งด้วย React
เมื่อสองปีก่อนฉันเขียนบทความเกี่ยวกับไฟล์ ตอบสนองระบบนิเวศ . ในประเด็นอื่น ๆ บทความนี้เป็นที่ถกเถียงกันอยู่ว่า Angular ได้กลายเป็นเหยื่อของ“ ความตายโดยการประกาศล่วงหน้า” ในตอนนั้นตัวเลือกระหว่าง Angular กับเกือบทุกอย่างเป็นเรื่องง่ายสำหรับทุกคนที่ไม่ต้องการให้โครงการของพวกเขาทำงานบนเฟรมเวิร์กที่ล้าสมัย Angular 1 ล้าสมัยและ Angular 2 ก็ไม่มีให้ใช้งานในเวอร์ชันอัลฟา
c corp vs s corp
ในการมองย้อนกลับไปความกลัวนั้นมีเหตุผลไม่มากก็น้อย Angular 2 เปลี่ยนไปอย่างมากและยังผ่านการเขียนซ้ำครั้งใหญ่ก่อนการเปิดตัวครั้งสุดท้าย
สองปีต่อมาเรามี Angular 4 พร้อมคำสัญญาว่าจะให้ความมั่นคงสัมพันธ์กันนับจากนี้เป็นต้นไป
ตอนนี้เป็นอย่างไร
บางคนบอกว่าการเปรียบเทียบ React กับ Angular ก็เหมือนกับการเปรียบเทียบแอปเปิ้ลกับส้ม ในขณะที่ไลบรารีหนึ่งคือไลบรารีที่เกี่ยวข้องกับมุมมอง แต่อีกอันเป็นเฟรมเวิร์กที่สมบูรณ์
แน่นอนที่สุด ตอบสนองนักพัฒนา จะเพิ่มไลบรารีสองสามไลบรารีเพื่อตอบสนองเพื่อเปลี่ยนเป็นเฟรมเวิร์กที่สมบูรณ์ จากนั้นอีกครั้งขั้นตอนการทำงานที่เป็นผลลัพธ์ของสแต็กนี้มักจะยังคงแตกต่างจาก Angular อยู่มากดังนั้นความสามารถในการเปรียบเทียบจึงยังคง จำกัด
ความแตกต่างที่ใหญ่ที่สุดอยู่ที่การจัดการของรัฐ Angular มาพร้อมกับการผูกข้อมูลที่รวมอยู่ในขณะที่ React ในปัจจุบันมักจะถูกเพิ่มโดย Redux เพื่อให้การไหลของข้อมูลทิศทางเดียวและทำงานกับข้อมูลที่ไม่เปลี่ยนทิศทาง สิ่งเหล่านี้กำลังต่อต้านแนวทางในสิทธิของตนเองและการอภิปรายนับไม่ถ้วนกำลังเกิดขึ้นว่าการผูกข้อมูลที่ไม่แน่นอน / ข้อมูลดีกว่าหรือแย่กว่าไม่เปลี่ยนทิศทาง / ทิศทางเดียว
เนื่องจาก React มีชื่อเสียงในการแฮ็คได้ง่ายขึ้นฉันจึงตัดสินใจสร้างการตั้งค่าการตอบสนองที่สะท้อน Angular อย่างใกล้ชิดเพื่อให้สามารถเปรียบเทียบข้อมูลโค้ดแบบเคียงข้างกันได้
คุณสมบัติเชิงมุมบางอย่างที่โดดเด่น แต่ไม่อยู่ในการตอบสนองตามค่าเริ่มต้นคือ:
ลักษณะเฉพาะ | แพ็คเกจเชิงมุม | ตอบสนองไลบรารี |
---|---|---|
การผูกข้อมูลการฉีดพึ่งพา (DI) | @ เชิงมุม / แกน | MobX |
คุณสมบัติที่คำนวณ | rxjs | MobX |
การกำหนดเส้นทางตามส่วนประกอบ | @ เชิงมุม / เราเตอร์ | ตอบสนองเราเตอร์ v4.0 |
ส่วนประกอบการออกแบบวัสดุ | @ เชิงมุม / วัสดุ | ตอบสนองกล่องเครื่องมือ |
CSS กำหนดขอบเขตไว้ที่ส่วนประกอบ | @ เชิงมุม / แกน | โมดูล CSS |
การตรวจสอบแบบฟอร์ม | @ เชิงมุม / แบบฟอร์ม | FormState |
เครื่องกำเนิดโครงการ | @ เชิงมุม / cli | ตอบสนองสคริปต์ TS |
การผูกข้อมูลสามารถเริ่มต้นด้วยเนื้อหาได้ง่ายกว่าวิธีการแบบทิศทางเดียว แน่นอนมันเป็นไปได้ที่จะไปในทิศทางตรงกันข้ามอย่างสิ้นเชิงและใช้ Redux หรือ mobx รัฐต้นไม้ ด้วย React และ ngrx ด้วย Angular แต่นั่นจะเป็นหัวข้อสำหรับโพสต์อื่น
ในขณะที่ประสิทธิภาพเป็นเรื่องที่น่ากังวล แต่การรับแบบธรรมดาใน Angular นั้นไม่ต้องสงสัยเลยเมื่อพวกเขาถูกเรียกในการแสดงผลแต่ละครั้ง เป็นไปได้ที่จะใช้ พฤติกรรม จาก RsJS ซึ่งทำงาน
ด้วย React คุณสามารถใช้ @ คอมพิวเตอร์ จาก MobX ซึ่งบรรลุวัตถุประสงค์เดียวกันโดยมี API ที่ดีกว่าเล็กน้อย
การฉีดแบบพึ่งพาเป็นเรื่องที่ถกเถียงกันเพราะมันขัดกับกระบวนทัศน์การตอบสนองในปัจจุบันของการเขียนโปรแกรมเชิงฟังก์ชันและการไม่เปลี่ยนรูป ปรากฎว่าการแทรกซึมแบบพึ่งพาบางประเภทแทบจะขาดไม่ได้ในสภาพแวดล้อมการผูกข้อมูลเนื่องจากช่วยในการแยกส่วน (และการเยาะเย้ยและการทดสอบ) โดยที่ไม่มีสถาปัตยกรรมชั้นข้อมูลแยกต่างหาก
ข้อดีอีกอย่างของ DI (รองรับใน Angular) คือความสามารถในการมีวงจรชีวิตที่แตกต่างกันของร้านค้าต่างๆ กระบวนทัศน์การตอบสนองในปัจจุบันส่วนใหญ่ใช้สถานะแอปทั่วโลกบางประเภทซึ่งจับคู่กับส่วนประกอบต่างๆ แต่จากประสบการณ์ของฉันการแนะนำจุดบกพร่องทั้งหมดนั้นง่ายเกินไปเมื่อทำความสะอาดสถานะทั่วโลกในการยกเลิกการต่อเชื่อมส่วนประกอบ
การมีร้านค้าที่สร้างขึ้นจากการติดตั้งส่วนประกอบ (และสามารถใช้ได้อย่างราบรื่นสำหรับเด็ก ๆ ของส่วนประกอบนี้) ดูเหมือนจะมีประโยชน์มากและมักถูกมองข้ามแนวคิด
นอกกรอบใน Angular แต่สามารถทำซ้ำได้อย่างง่ายดายด้วย MobX เช่นกัน
การกำหนดเส้นทางตามคอมโพเนนต์ช่วยให้คอมโพเนนต์สามารถจัดการเส้นทางย่อยของตนเองแทนที่จะมีการกำหนดค่าเราเตอร์ส่วนกลางขนาดใหญ่เพียงรายการเดียว ในที่สุดแนวทางนี้ก็เป็น react-router
ในเวอร์ชัน 4
เป็นเรื่องดีเสมอที่จะเริ่มต้นด้วยส่วนประกอบระดับสูงขึ้นไปและการออกแบบวัสดุได้กลายเป็นสิ่งที่เหมือนกับตัวเลือกเริ่มต้นที่ยอมรับกันทั่วไปแม้ในโครงการที่ไม่ใช่ของ Google
ฉันจงใจเลือก ตอบสนองกล่องเครื่องมือ มากกว่าที่แนะนำโดยปกติ UI วัสดุ เนื่องจาก UI วัสดุได้สารภาพตัวเองอย่างจริงจัง ปัญหาด้านประสิทธิภาพ ด้วยแนวทาง inline-CSS ซึ่งพวกเขาวางแผนที่จะแก้ปัญหาในเวอร์ชันถัดไป
นอกจากนี้ PostCSS / cssnext ที่ใช้ใน React Toolbox กำลังเริ่มแทนที่ Sass / LESS อยู่ดี
คลาส CSS เป็นตัวแปรส่วนกลาง มีหลายวิธีในการจัดระเบียบ CSS เพื่อป้องกันความขัดแย้ง (รวมถึง ดี ) แต่มีแนวโน้มที่ชัดเจนในการใช้ไลบรารีที่ช่วยประมวลผล CSS เพื่อป้องกันความขัดแย้งเหล่านั้นโดยไม่ต้องใช้ไฟล์ นักพัฒนาส่วนหน้า เพื่อประดิษฐ์ระบบการตั้งชื่อ CSS อย่างละเอียด
การตรวจสอบแบบฟอร์มเป็นคุณสมบัติที่ไม่สำคัญและใช้กันอย่างแพร่หลาย ดีที่มีไลบรารีอยู่เพื่อป้องกันการซ้ำซ้อนของโค้ดและบั๊ก
การมีเครื่องกำเนิด CLI สำหรับโปรเจ็กต์นั้นสะดวกกว่าการโคลนบอยเลอร์เพลทจาก GitHub
ดังนั้นเราจะสร้างแอปพลิเคชันเดียวกันใน React และ Angular ไม่มีอะไรน่าตื่นเต้นมีเพียง Shoutboard ที่ให้ทุกคนสามารถโพสต์ข้อความไปยังเพจทั่วไป
คุณสามารถทดลองใช้งานได้ที่นี่:
หากคุณต้องการมีซอร์สโค้ดทั้งหมดคุณสามารถรับได้จาก GitHub:
คุณจะสังเกตเห็นว่าเราได้ใช้ TypeScript สำหรับแอป React ด้วย ข้อดีของการตรวจสอบประเภทใน TypeScript นั้นชัดเจน และตอนนี้เนื่องจากการจัดการการนำเข้าที่ดีขึ้น async / a waiting และ rest สเปรดก็มาถึง TypeScript 2 ในที่สุดก็ออกจาก Babel / ES7 / ไหล ในฝุ่น
นอกจากนี้เราจะเพิ่ม ลูกค้าของ Apollo ทั้งสองอย่างเพราะเราต้องการใช้ GraphQL ฉันหมายความว่า REST นั้นยอดเยี่ยมมาก แต่หลังจากผ่านไปสักสิบปีมันก็เก่าลง
ก่อนอื่นมาดูจุดเข้าใช้งานของทั้งสองแอปพลิเคชัน
เชิงมุม
const appRoutes: Routes = [ { path: 'home', component: HomeComponent }, { path: 'posts', component: PostsComponent }, { path: 'form', component: FormComponent }, { path: '', redirectTo: '/home', pathMatch: 'full' } ] @NgModule({ declarations: [ AppComponent, PostsComponent, HomeComponent, FormComponent, ], imports: [ BrowserModule, RouterModule.forRoot(appRoutes), ApolloModule.forRoot(provideClient), FormsModule, ReactiveFormsModule, HttpModule, BrowserAnimationsModule, MdInputModule, MdSelectModule, MdButtonModule, MdCardModule, MdIconModule ], providers: [ AppService ], bootstrap: [AppComponent] })
@Injectable() export class AppService { username = 'Mr. User' }
โดยพื้นฐานแล้วส่วนประกอบทั้งหมดที่เราต้องการใช้ในแอปพลิเคชันจำเป็นต้องไปที่การประกาศ ไลบรารีของบุคคลที่สามทั้งหมดที่จะนำเข้าและร้านค้าทั่วโลกทั้งหมดไปยังผู้ให้บริการ ส่วนประกอบของเด็ก ๆ สามารถเข้าถึงสิ่งเหล่านี้ได้โดยมีโอกาสที่จะเพิ่มสิ่งของในท้องถิ่นมากขึ้น
ตอบสนอง
const appStore = AppStore.getInstance() const routerStore = RouterStore.getInstance() const rootStores = { appStore, routerStore } ReactDOM.render( , document.getElementById('root') )
ส่วนประกอบนี้ใช้สำหรับการฉีดแบบพึ่งพาใน MobX จะบันทึกร้านค้าตามบริบทเพื่อให้ส่วนประกอบของปฏิกิริยาสามารถฉีดเข้าไปในภายหลังได้ ใช่บริบทการตอบสนองสามารถใช้ (เนื้อหา) ได้ อย่างปลอดภัย .
เวอร์ชัน React นั้นสั้นกว่าเล็กน้อยเนื่องจากไม่มีการประกาศโมดูล - โดยปกติคุณเพียงแค่นำเข้าและพร้อมใช้งาน บางครั้งการพึ่งพาอย่างหนักแบบนี้ไม่เป็นที่ต้องการ (การทดสอบ) ดังนั้นสำหรับร้านซิงเกิลตันทั่วโลกฉันต้องใช้อายุหลายสิบปีนี้ GoF รูปแบบ :
export class AppStore { static instance: AppStore static getInstance() @observable username = 'Mr. User' }
Angular’s Router สามารถฉีดได้ดังนั้นจึงสามารถใช้งานได้จากทุกที่ไม่เพียง แต่ส่วนประกอบเท่านั้น เพื่อให้ได้ปฏิกิริยาเดียวกันเราใช้ mobx-react-router แพคเกจและฉีด routerStore
.
สรุป: การบูตแอปพลิเคชันทั้งสองค่อนข้างตรงไปตรงมา React มีขอบที่ง่ายกว่าโดยใช้เพียงการนำเข้าแทนโมดูล แต่อย่างที่เราจะเห็นในภายหลังโมดูลเหล่านั้นมีประโยชน์มากทีเดียว การทำเสื้อกล้ามด้วยตนเองเป็นเรื่องที่น่ารำคาญเล็กน้อย สำหรับไวยากรณ์การประกาศเส้นทาง JSON เทียบกับ JSX เป็นเพียงเรื่องของการตั้งค่า
ดังนั้นจึงมีสองกรณีสำหรับการเปลี่ยนเส้นทาง ประกาศโดยใช้
องค์ประกอบและความจำเป็นเรียก API การกำหนดเส้นทาง (และตำแหน่งที่ตั้ง) โดยตรง
เชิงมุม
Home Posts {this.props.children}
React Router ยังสามารถตั้งค่าคลาสของลิงค์ที่ใช้งานได้ด้วย activeClassName
ที่นี่เราไม่สามารถระบุชื่อคลาสได้โดยตรงเนื่องจากคอมไพเลอร์โมดูล CSS สร้างขึ้นโดยเฉพาะและเราจำเป็นต้องใช้ style
ผู้ช่วย. เพิ่มเติมในภายหลัง
ดังที่เห็นด้านบน React Router ใช้องค์ประกอบภายใน anelement เนื่องจากองค์ประกอบเพียงแค่ห่อและต่อเชื่อมเส้นทางปัจจุบันหมายความว่าเส้นทางย่อยของส่วนประกอบปัจจุบันเป็นเพียง this.props.children
นั่นก็ประกอบได้เช่นกัน
export class FormStore { routerStore: RouterStore constructor() { this.routerStore = RouterStore.getInstance() } goBack = () => { this.routerStore.history.push('/posts') } }
mobx-router-store
แพ็คเกจยังช่วยให้การฉีดและการนำทางทำได้ง่าย
สรุป: ทั้งสองวิธีในการกำหนดเส้นทางมีความคล้ายคลึงกันมาก Angular ดูเหมือนจะใช้งานง่ายกว่าในขณะที่ React Router มีความสามารถในการประกอบที่ตรงไปตรงมากว่าเล็กน้อย
ได้รับการพิสูจน์แล้วว่ามีประโยชน์ในการแยกชั้นข้อมูลออกจากชั้นการนำเสนอ สิ่งที่เราพยายามบรรลุด้วย DI ในที่นี้คือการสร้างส่วนประกอบของชั้นข้อมูล (ในที่นี้เรียกว่าโมเดล / ร้านค้า / บริการ) ตามวงจรชีวิตของส่วนประกอบภาพและทำให้สามารถสร้างส่วนประกอบดังกล่าวหนึ่งหรือหลายอินสแตนซ์ได้โดยไม่จำเป็นต้องสัมผัสทั่วโลก สถานะ. นอกจากนี้ควรผสมและจับคู่ข้อมูลและเลเยอร์การแสดงภาพที่เข้ากันได้
ตัวอย่างในบทความนี้ง่ายมากดังนั้นสิ่งต่างๆของ DI ทั้งหมดอาจดูเหมือนมากเกินไป แต่ก็มีประโยชน์เมื่อแอปพลิเคชันเติบโตขึ้น
เชิงมุม
@Injectable() export class HomeService { message = 'Welcome to home page' counter = 0 increment() { this.counter++ } }
ดังนั้นคลาสใดก็ได้ที่สามารถสร้าง @injectable
และคุณสมบัติและวิธีการที่มีให้สำหรับส่วนประกอบ
@Component({ selector: 'app-home', templateUrl: './home.component.html', providers: [ HomeService ] }) export class HomeComponent { constructor( public homeService: HomeService, public appService: AppService, ) { } }
โดยการลงทะเบียน HomeService
สำหรับคอมโพเนนต์ providers
เราทำให้พร้อมใช้งานสำหรับคอมโพเนนต์นี้โดยเฉพาะ ตอนนี้ไม่ใช่ซิงเกิลตัน แต่แต่ละอินสแตนซ์ของคอมโพเนนต์จะได้รับสำเนาใหม่ที่เมาท์ของคอมโพเนนต์ นั่นหมายความว่าไม่มีข้อมูลเก่าจากการใช้งานครั้งก่อน
ในทางตรงกันข้าม AppService
ได้รับการลงทะเบียนกับ app.module
(ดูด้านบน) ดังนั้นจึงเป็นซิงเกิลตันและยังคงเหมือนเดิมสำหรับส่วนประกอบทั้งหมดแม้ว่าอายุการใช้งานของแอปพลิเคชัน ความสามารถในการควบคุมวงจรชีวิตของบริการจากส่วนประกอบเป็นแนวคิดที่มีประโยชน์มาก แต่ไม่ได้รับการชื่นชม
DI ทำงานโดยกำหนดอินสแตนซ์บริการให้กับตัวสร้างของคอมโพเนนต์ซึ่งระบุโดยประเภท TypeScript นอกจากนี้ public
คีย์เวิร์ดจะกำหนดพารามิเตอร์ให้กับ this
โดยอัตโนมัติเพื่อที่เราจะได้ไม่ต้องเขียนสิ่งที่น่าเบื่อ this.homeService = homeService
เส้นอีกต่อไป
Dashboard
Clicks since last visit: {{homeService.counter}} Click!
ไวยากรณ์ของเทมเพลต Angular มีเนื้อหาค่อนข้างหรูหรา ฉันชอบ [()]
ช็อตคัทซึ่งทำงานเหมือนการเชื่อมข้อมูลแบบ 2 ทาง แต่ภายใต้ประทุนนั้นแท้จริงแล้วมันคือแอ็ตทริบิวต์การโยง + เหตุการณ์ ตามวงจรชีวิตของบริการของเรา homeService.counter
จะรีเซ็ตทุกครั้งที่เราออกจาก /home
แต่ appService.username
อยู่และสามารถเข้าถึงได้จากทุกที่
ตอบสนอง
import { observable } from 'mobx' export class HomeStore { @observable counter = 0 increment = () => { this.counter++ } }
ด้วย MobX เราต้องเพิ่ม @observable
มัณฑนากรของสถานที่ให้บริการใด ๆ ที่เราต้องการให้สังเกตได้
@observer export class Home extends React.Component { homeStore: HomeStore componentWillMount() { this.homeStore = new HomeStore() } render() { return } }
ในการจัดการวงจรชีวิตอย่างถูกต้องเราต้องทำงานมากกว่าในตัวอย่าง Angular เล็กน้อย เราห่อ HomeComponent
ภายใน a Provider
ซึ่งได้รับอินสแตนซ์ใหม่ของ HomeStore
ในแต่ละเมานต์
interface HomeComponentProps { appStore?: AppStore, homeStore?: HomeStore } @inject('appStore', 'homeStore') @observer export class HomeComponent extends React.Component { render() { const { homeStore, appStore } = this.props return Dashboard
Clicks since last visit: {homeStore.counter} Click! } }
HomeComponent
ใช้ @observer
มัณฑนากรเพื่อรับฟังการเปลี่ยนแปลงใน @observable
คุณสมบัติ.
กลไกใต้ฝากระโปรงของสิ่งนี้ค่อนข้างน่าสนใจดังนั้นเรามาดูคร่าวๆกันดีกว่า @observable
มัณฑนากรแทนที่คุณสมบัติในออบเจ็กต์ด้วย getter และ setter ซึ่งอนุญาตให้ดักฟังการโทร เมื่อฟังก์ชันการแสดงผลของ @observer
ส่วนประกอบที่เพิ่มขึ้นเรียกว่าคุณสมบัติเหล่านั้นจะถูกเรียกและพวกมันยังคงอ้างอิงถึงส่วนประกอบที่เรียกมัน
จากนั้นเมื่อมีการเรียกใช้ setter และมีการเปลี่ยนแปลงค่าฟังก์ชัน render ของส่วนประกอบที่ใช้คุณสมบัติในการเรนเดอร์ล่าสุดจะถูกเรียก ตอนนี้ข้อมูลเกี่ยวกับคุณสมบัติที่ใช้ในการอัปเดตและวงจรทั้งหมดสามารถเริ่มต้นใหม่ได้
กลไกที่ง่ายมากและมีประสิทธิภาพมากเช่นกัน คำอธิบายเชิงลึกเพิ่มเติม ที่นี่ .
@inject
มัณฑนากรใช้ฉีด appStore
และ homeStore
อินสแตนซ์ในอุปกรณ์ประกอบฉากของ HomeComponent
ณ จุดนี้แต่ละร้านมีวงจรชีวิตที่แตกต่างกัน appStore
จะเหมือนกันในช่วงชีวิตของแอปพลิเคชัน แต่ homeStore
ถูกสร้างขึ้นใหม่ในการนำทางไปยังเส้นทาง“ / home” แต่ละครั้ง
ข้อดีของการนี้คือไม่จำเป็นต้องทำความสะอาดคุณสมบัติด้วยตนเองเนื่องจากเป็นกรณีที่ร้านค้าทั้งหมดอยู่ทั่วโลกซึ่งเป็นเรื่องที่น่าปวดหัวหากเส้นทางเป็นหน้า 'รายละเอียด' บางส่วนที่มีข้อมูลที่แตกต่างกันอย่างสิ้นเชิงในแต่ละครั้ง
สรุป: เนื่องจากการจัดการวงจรชีวิตของผู้ให้บริการในคุณลักษณะโดยธรรมชาติของ Angular’s DI แน่นอนว่าจะทำได้ง่ายกว่าที่นั่น นอกจากนี้ยังสามารถใช้เวอร์ชัน React ได้ แต่เกี่ยวข้องกับสำเร็จรูปมากกว่า
ตอบสนอง
มาเริ่มกันที่ React ซึ่งมีวิธีแก้ปัญหาที่ตรงไปตรงมามากขึ้น
import { observable, computed, action } from 'mobx' export class HomeStore { import { observable, computed, action } from 'mobx' export class HomeStore { @observable counter = 0 increment = () => { this.counter++ } @computed get counterMessage() { console.log('recompute counterMessage!') return `${this.counter} ${this.counter === 1 ? 'click' : 'clicks'} since last visit` } }
ดังนั้นเราจึงมีคุณสมบัติที่คำนวณซึ่งเชื่อมโยงกับ counter
และส่งกลับข้อความที่เป็นพหูพจน์อย่างถูกต้อง ผลลัพธ์ของ counterMessage
ถูกแคชและจะคำนวณใหม่เมื่อ counter
เท่านั้น การเปลี่ยนแปลง
{homeStore.counterMessage} Click!
จากนั้นเราอ้างอิงคุณสมบัติ (และ increment
วิธีการ) จากเทมเพลต JSX ฟิลด์อินพุตถูกขับเคลื่อนโดยการผูกกับค่าและปล่อยให้เมธอดจาก appStore
จัดการเหตุการณ์ของผู้ใช้
เชิงมุม
ภาษาโปรแกรม c คืออะไร
เพื่อให้ได้เอฟเฟกต์เดียวกันใน Angular เราต้องมีความคิดสร้างสรรค์มากขึ้น
import { Injectable } from '@angular/core' import { BehaviorSubject } from 'rxjs/BehaviorSubject' @Injectable() export class HomeService { message = 'Welcome to home page' counterSubject = new BehaviorSubject(0) // Computed property can serve as basis for further computed properties counterMessage = new BehaviorSubject('') constructor() { // Manually subscribe to each subject that couterMessage depends on this.counterSubject.subscribe(this.recomputeCounterMessage) } // Needs to have bound this private recomputeCounterMessage = (x) => { console.log('recompute counterMessage!') this.counterMessage.next(`${x} ${x === 1 ? 'click' : 'clicks'} since last visit`) } increment() { this.counterSubject.next(this.counterSubject.getValue() + 1) } }
เราจำเป็นต้องกำหนดค่าทั้งหมดที่ใช้เป็นพื้นฐานสำหรับคุณสมบัติที่คำนวณเป็น BehaviorSubject
คุณสมบัติที่คำนวณเองก็เป็น BehaviorSubject
เช่นกันเนื่องจากคุณสมบัติใด ๆ ที่คำนวณสามารถใช้เป็นอินพุตสำหรับคุณสมบัติที่คำนวณอื่นได้
แน่นอน RxJS
ทำได้ ล้นหลาม มากกว่าแค่นี้ แต่นั่นจะเป็นหัวข้อสำหรับบทความที่แตกต่างไปจากเดิมอย่างสิ้นเชิง ข้อเสียเล็กน้อยคือการใช้ RxJS เพียงเล็กน้อยสำหรับคุณสมบัติที่คำนวณเพียงเล็กน้อยนั้นค่อนข้างละเอียดกว่าตัวอย่างปฏิกิริยาและคุณต้องจัดการการสมัครสมาชิกด้วยตนเอง (เช่นที่นี่ในตัวสร้าง)
{homeService.counterMessage } Click!
สังเกตว่าเราสามารถอ้างอิงหัวเรื่อง RxJS ด้วย | async
ได้อย่างไร ท่อ. นั่นเป็นสัมผัสที่ดีสั้นกว่าที่ต้องสมัครสมาชิกในส่วนประกอบของคุณ input
คอมโพเนนต์ขับเคลื่อนโดย [(ngModel)]
คำสั่ง แม้จะดูแปลกตา แต่ก็ดูหรูหรามากทีเดียว เพียงแค่น้ำตาลเชิงไวยากรณ์สำหรับการผูกข้อมูลของค่ากับ appService.username
และกำหนดค่าอัตโนมัติจากเหตุการณ์ป้อนข้อมูลของผู้ใช้
สรุป: คุณสมบัติที่คำนวณนั้นง่ายต่อการใช้งานใน React / MobX มากกว่าใน Angular / RxJS แต่ RxJS อาจมีคุณสมบัติ FRP ที่มีประโยชน์มากกว่าซึ่งอาจได้รับการชื่นชมในภายหลัง
หากต้องการแสดงให้เห็นว่าเทมเพลตซ้อนทับกันอย่างไรให้ใช้คอมโพเนนต์โพสต์ที่แสดงรายการโพสต์
เชิงมุม
@Component({ selector: 'app-posts', templateUrl: './posts.component.html', styleUrls: ['./posts.component.css'], providers: [ PostsService ] }) export class PostsComponent implements OnInit { constructor( public postsService: PostsService, public appService: AppService ) { } ngOnInit() { this.postsService.initializePosts() } }
ส่วนประกอบนี้เชื่อมโยง HTML, CSS และบริการที่แทรกเข้าด้วยกันและยังเรียกใช้ฟังก์ชันเพื่อโหลดโพสต์จาก API ในการเริ่มต้น AppService
เป็นซิงเกิลตันที่กำหนดไว้ในโมดูลแอปพลิเคชันในขณะที่ PostsService
เป็นแบบชั่วคราวโดยมีอินสแตนซ์ใหม่ที่สร้างขึ้นในแต่ละครั้งที่สร้างส่วนประกอบ CSS ที่อ้างอิงจากคอมโพเนนต์นี้จะถูกกำหนดขอบเขตให้กับคอมโพเนนต์นี้ซึ่งหมายความว่าเนื้อหาไม่สามารถส่งผลกระทบใด ๆ นอกคอมโพเนนต์
add Hello {{appService.username}}
{{post.title}} {{post.name}} {{post.message}}
ในเทมเพลต HTML เราอ้างอิงส่วนประกอบส่วนใหญ่จาก Angular Material เพื่อให้พร้อมใช้งานจำเป็นต้องรวมไว้ใน app.module
การนำเข้า (ดูด้านบน) *ngFor
คำสั่งใช้เพื่อทำซ้ำ md-card
ส่วนประกอบสำหรับแต่ละโพสต์
CSS ในเครื่อง:
.mat-card { margin-bottom: 1rem; }
CSS ในเครื่องเพียงแค่เพิ่มหนึ่งในคลาสที่มีอยู่บน md-card
ส่วนประกอบ.
CSS ส่วนกลาง:
.float-right { float: right; }
คลาสนี้ถูกกำหนดใน global style.css
ไฟล์เพื่อให้พร้อมใช้งานสำหรับส่วนประกอบทั้งหมด สามารถอ้างอิงได้ด้วยวิธีมาตรฐาน class='float-right'
CSS ที่รวบรวม:
.float-right { float: right; } .mat-card[_ngcontent-c1] { margin-bottom: 1rem; }
ใน CSS ที่คอมไพล์แล้วเราจะเห็นว่า CSS ในเครื่องถูกกำหนดขอบเขตไปยังส่วนประกอบที่แสดงผลโดยใช้ [_ngcontent-c1]
ตัวเลือกแอตทริบิวต์ ทุกองค์ประกอบเชิงมุมที่แสดงผลมีคลาสที่สร้างขึ้นเช่นนี้เพื่อวัตถุประสงค์ในการกำหนดขอบเขต CSS
ข้อดีของกลไกนี้คือเราสามารถอ้างอิงคลาสได้ตามปกติและการกำหนดขอบเขตจะถูกจัดการ“ ภายใต้ประทุน”
ตอบสนอง
import * as style from './posts.css' import * as appStyle from '../app.css' @observer export class Posts extends React.Component { postsStore: PostsStore componentWillMount() { this.postsStore = new PostsStore() this.postsStore.initializePosts() } render() { return } }
ใน React อีกครั้งเราต้องใช้ Provider
แนวทางการสร้าง PostsStore
การพึ่งพา 'ชั่วคราว' นอกจากนี้เรายังนำเข้ารูปแบบ CSS ซึ่งอ้างอิงเป็น style
และ appStyle
เพื่อให้สามารถใช้คลาสจากไฟล์ CSS เหล่านั้นใน JSX
การปรับแต่งประสิทธิภาพใน sql server 2012
interface PostsComponentProps { appStore?: AppStore, postsStore?: PostsStore } @inject('appStore', 'postsStore') @observer export class PostsComponent extends React.Component { render() { const { postsStore, appStore } = this.props return Hello {appStore.username}
{postsStore.posts.map(post => {post.message} )} } }
โดยปกติแล้ว JSX ให้ความรู้สึกกับ JavaScript-y มากกว่าเทมเพลต HTML ของ Angular ซึ่งอาจเป็นสิ่งที่ดีหรือไม่ดีขึ้นอยู่กับรสนิยมของคุณ แทน *ngFor
คำสั่งเราใช้ map
สร้างเพื่อวนซ้ำโพสต์
ตอนนี้ Angular อาจเป็นเฟรมเวิร์กที่ดึงดูด TypeScript มากที่สุด แต่จริงๆแล้วมันเป็น JSX ที่ TypeScript โดดเด่นจริงๆ ด้วยการเพิ่มโมดูล CSS (นำเข้าด้านบน) มันจะเปลี่ยนการเข้ารหัสเทมเพลตของคุณเป็นเซนการเติมโค้ด ทุกสิ่งจะถูกตรวจสอบประเภท ส่วนประกอบแอตทริบิวต์แม้แต่คลาส CSS (appStyle.floatRight
และ style.messageCard
ดูด้านล่าง) และแน่นอนลักษณะแบบลีนของ JSX สนับสนุนให้แบ่งออกเป็นส่วนประกอบและส่วนย่อยมากกว่าเทมเพลตของ Angular เล็กน้อย
CSS ในเครื่อง:
.messageCard { margin-bottom: 1rem; }
CSS ส่วนกลาง:
.floatRight { float: right; }
CSS ที่รวบรวม:
.floatRight__qItBM { float: right; } .messageCard__1Dt_9 { margin-bottom: 1rem; }
อย่างที่คุณเห็นตัวโหลดโมดูล CSS จะทำการแก้ไข CSS แต่ละคลาสด้วย postfix แบบสุ่มซึ่งรับประกันความเป็นเอกลักษณ์ วิธีที่ตรงไปตรงมาเพื่อหลีกเลี่ยงความขัดแย้ง จากนั้นคลาสจะถูกอ้างอิงผ่านอ็อบเจ็กต์ที่นำเข้า Webpack ข้อเสียเปรียบประการหนึ่งที่เป็นไปได้คือคุณไม่สามารถสร้าง CSS ด้วยคลาสและเพิ่มระดับได้เหมือนที่เราทำในตัวอย่าง Angular ในทางกลับกันนี่อาจเป็นสิ่งที่ดีเพราะมันบังคับให้คุณต้องห่อหุ้มสไตล์ให้เหมาะสม
สรุป: โดยส่วนตัวแล้วฉันชอบ JSX มากกว่าเทมเพลต Angular โดยเฉพาะอย่างยิ่งเนื่องจากการเติมโค้ดและการสนับสนุนการตรวจสอบประเภท นั่นเป็นคุณสมบัติของนักฆ่าจริงๆ ตอนนี้ Angular มีคอมไพเลอร์ AOT ซึ่งสามารถมองเห็นบางสิ่งได้เช่นกันการเติมโค้ดยังใช้งานได้ประมาณครึ่งหนึ่งของสิ่งที่นั่น แต่ยังไม่สมบูรณ์เท่า JSX / TypeScript
ดังนั้นเราจึงตัดสินใจใช้ GraphQL เพื่อจัดเก็บข้อมูลสำหรับแอปพลิเคชันนี้ วิธีที่ง่ายที่สุดวิธีหนึ่งในการสร้างแบ็คเอนด์ของ GraphQL คือการใช้ BaaS บางตัวเช่น Graphcool นั่นคือสิ่งที่เราทำ โดยพื้นฐานแล้วคุณเพียงแค่กำหนดโมเดลและคุณสมบัติและ CRUD ของคุณก็พร้อมใช้งาน
รหัสทั่วไป
เนื่องจากโค้ดที่เกี่ยวข้องกับ GraphQL บางส่วนเหมือนกัน 100% สำหรับการใช้งานทั้งสองอย่างจึงไม่ควรทำซ้ำสองครั้ง:
const PostsQuery = gql` query PostsQuery { allPosts(orderBy: createdAt_DESC, first: 5) { id, name, title, message } } `
GraphQL เป็นภาษาสืบค้นที่มีวัตถุประสงค์เพื่อมอบชุดฟังก์ชันที่สมบูรณ์ยิ่งขึ้นเมื่อเทียบกับจุดสิ้นสุด RESTful แบบคลาสสิก มาดูคำค้นหานี้กัน
PostsQuery
เป็นเพียงชื่อสำหรับการสืบค้นนี้เพื่ออ้างอิงในภายหลังสามารถตั้งชื่ออะไรก็ได้allPosts
เป็นส่วนที่สำคัญที่สุด - อ้างอิงถึงฟังก์ชันเพื่อสืบค้นระเบียนทั้งหมดด้วยโมเดล 'โพสต์' ชื่อนี้สร้างโดย GraphcoolorderBy
และ first
เป็นพารามิเตอร์ของ allPosts
ฟังก์ชัน createdAt
เป็นหนึ่งใน Post
คุณลักษณะของโมเดล first: 5
หมายความว่าจะส่งคืนผลลัพธ์ 5 รายการแรกของข้อความค้นหาid
, name
, title
และ message
เป็นคุณลักษณะของ Post
แบบจำลองที่เราต้องการรวมไว้ในผลลัพธ์ แอตทริบิวต์อื่น ๆ จะถูกกรองออกอย่างที่คุณเห็นแล้วว่ามันทรงพลังมาก เช็คเอาท์ หน้านี้ เพื่อทำความคุ้นเคยกับแบบสอบถาม GraphQL มากขึ้น
interface Post { id: string name: string title: string message: string } interface PostsQueryResult { allPosts: Array }
ใช่ในฐานะพลเมือง TypeScript ที่ดีเราสร้างอินเทอร์เฟซสำหรับผลลัพธ์ GraphQL
เชิงมุม
@Injectable() export class PostsService { posts = [] constructor(private apollo: Apollo) { } initializePosts() { this.apollo.query({ query: PostsQuery, fetchPolicy: 'network-only' }).subscribe(({ data }) => { this.posts = data.allPosts }) } }
แบบสอบถาม GraphQL เป็น RxJS ที่สังเกตได้และเราสมัครรับข้อมูล มันใช้งานได้เหมือนคำสัญญา แต่ไม่มากดังนั้นเราจึงโชคไม่ดีที่ใช้ async/await
แน่นอนว่ายังมี สัญญา แต่ดูเหมือนจะไม่ใช่วิธีเชิงมุมอยู่ดี เราตั้ง fetchPolicy: 'network-only'
เนื่องจากในกรณีนี้เราไม่ต้องการแคชข้อมูล แต่จะดึงข้อมูลใหม่ทุกครั้ง
ตอบสนอง
export class PostsStore { appStore: AppStore @observable posts: Array = [] constructor() { this.appStore = AppStore.getInstance() } async initializePosts() { const result = await this.appStore.apolloClient.query({ query: PostsQuery, fetchPolicy: 'network-only' }) this.posts = result.data.allPosts } }
เวอร์ชัน React เกือบจะเหมือนกัน แต่เป็น apolloClient
ที่นี่ใช้คำสัญญาเราสามารถใช้ประโยชน์จาก async/await
ไวยากรณ์ มีวิธีการอื่น ๆ ในการตอบสนองที่เพียงแค่ 'เทป' การสอบถาม GraphQL ไป ส่วนประกอบลำดับที่สูงขึ้น แต่สำหรับฉันแล้วดูเหมือนว่าการผสมผสานข้อมูลและเลเยอร์การนำเสนอเข้าด้วยกันมากเกินไป
สรุป: แนวคิดของ RxJS subscribe vs. async / await นั้นค่อนข้างเหมือนกัน
รหัสทั่วไป
อีกครั้งรหัสที่เกี่ยวข้องกับ GraphQL:
const AddPostMutation = gql` mutation AddPostMutation($name: String!, $title: String!, $message: String!) { createPost( name: $name, title: $title, message: $message ) { id } } `
วัตถุประสงค์ของการกลายพันธุ์คือการสร้างหรืออัปเดตเรกคอร์ด ดังนั้นจึงเป็นประโยชน์ที่จะประกาศตัวแปรบางตัวที่มีการกลายพันธุ์เนื่องจากเป็นวิธีการส่งผ่านข้อมูลไปยังตัวแปรนั้น ดังนั้นเราจึงมี name
, title
และ message
ตัวแปรที่พิมพ์เป็น String
ซึ่งเราต้องเติมทุกครั้งที่เราเรียกสิ่งนี้ว่าการกลายพันธุ์ createPost
ฟังก์ชันอีกครั้งถูกกำหนดโดย Graphcool เราระบุว่า Post
คีย์ของโมเดลจะมีค่าจากตัวแปรการกลายพันธุ์และเราต้องการเพียงแค่ id
ของโพสต์ที่สร้างขึ้นใหม่เพื่อส่งกลับ
เชิงมุม
@Injectable() export class FormService { constructor( private apollo: Apollo, private router: Router, private appService: AppService ) { } addPost(value) { this.apollo.mutate({ mutation: AddPostMutation, variables: { name: this.appService.username, title: value.title, message: value.message } }).subscribe(({ data }) => { this.router.navigate(['/posts']) }, (error) => { console.log('there was an error sending the query', error) }) } }
เมื่อเรียก apollo.mutate
เราจำเป็นต้องให้การกลายพันธุ์ที่เราเรียกและตัวแปรด้วย เราได้ผลลัพธ์ใน subscribe
โทรกลับและใช้ฉีด router
เพื่อกลับไปที่รายการโพสต์
ตอบสนอง
export class FormStore { constructor() { this.appStore = AppStore.getInstance() this.routerStore = RouterStore.getInstance() this.postFormState = new PostFormState() } submit = async () => { await this.postFormState.form.validate() if (this.postFormState.form.error) return const result = await this.appStore.apolloClient.mutate( { mutation: AddPostMutation, variables: { name: this.appStore.username, title: this.postFormState.title.value, message: this.postFormState.message.value } } ) this.goBack() } goBack = () => { this.routerStore.history.push('/posts') } }
คล้ายกับข้างต้นมากโดยมีความแตกต่างของการฉีดขึ้นต่อกันแบบ 'ด้วยตนเอง' และการใช้ async/await
สรุป: อีกครั้งไม่แตกต่างกันมากที่นี่ สมัครสมาชิกกับ async / await นั้นโดยพื้นฐานแล้วสิ่งที่แตกต่างกัน
เราต้องการบรรลุเป้าหมายต่อไปนี้ด้วยแบบฟอร์มในแอปพลิเคชันนี้:
ตอบสนอง
export const check = (validator, message, options) => (value) => (!validator(value, options) && message) export const checkRequired = (msg: string) => check(nonEmpty, msg) export class PostFormState { title = new FieldState('').validators( checkRequired('Title is required'), check(isLength, 'Title must be at least 4 characters long.', { min: 4 }), check(isLength, 'Title cannot be more than 24 characters long.', { max: 24 }), ) message = new FieldState('').validators( checkRequired('Message cannot be blank.'), check(isLength, 'Message is too short, minimum is 50 characters.', { min: 50 }), check(isLength, 'Message is too long, maximum is 1000 characters.', { max: 1000 }), ) form = new FormState({ title: this.title, message: this.message }) }
ดังนั้น formstate ไลบรารีทำงานดังนี้: สำหรับแต่ละฟิลด์ในฟอร์มของคุณคุณกำหนด a FieldState
พารามิเตอร์ที่ส่งผ่านคือค่าเริ่มต้น validators
คุณสมบัติรับฟังก์ชันซึ่งส่งคืน 'เท็จ' เมื่อค่าถูกต้องและข้อความตรวจสอบความถูกต้องเมื่อค่าไม่ถูกต้อง ด้วย check
และ checkRequired
ฟังก์ชั่นตัวช่วยทุกอย่างสามารถเปิดเผยได้อย่างสวยงาม
เพื่อให้มีการตรวจสอบความถูกต้องสำหรับทั้งแบบฟอร์มคุณควรรวมฟิลด์เหล่านั้นด้วย FormState
อินสแตนซ์ซึ่งจะให้ความถูกต้องโดยรวม
@inject('appStore', 'formStore') @observer export class FormComponent extends React.Component { render() { const { appStore, formStore } = this.props const { postFormState } = formStore return Create a new post
You are now posting as {appStore.username}
FormState
อินสแตนซ์ให้ value
, onChange
และ error
คุณสมบัติซึ่งสามารถใช้กับส่วนประกอบส่วนหน้าได้อย่างง่ายดาย
} }
เมื่อ form.hasError
คือ true
เราปิดการใช้งานปุ่มไว้ ปุ่มส่งจะส่งแบบฟอร์มไปยังการกลายพันธุ์ของ GraphQL ที่นำเสนอก่อนหน้านี้
เชิงมุม
ใน Angular เราจะใช้ FormService
และ FormBuilder
ซึ่งเป็นส่วนหนึ่งของ @angular/forms
แพ็คเกจ
@Component({ selector: 'app-form', templateUrl: './form.component.html', providers: [ FormService ] }) export class FormComponent { postForm: FormGroup validationMessages = { 'title': { 'required': 'Title is required.', 'minlength': 'Title must be at least 4 characters long.', 'maxlength': 'Title cannot be more than 24 characters long.' }, 'message': { 'required': 'Message cannot be blank.', 'minlength': 'Message is too short, minimum is 50 characters', 'maxlength': 'Message is too long, maximum is 1000 characters' } }
ขั้นแรกให้กำหนดข้อความตรวจสอบความถูกต้อง
constructor( private router: Router, private formService: FormService, public appService: AppService, private fb: FormBuilder, ) { this.createForm() } createForm() { this.postForm = this.fb.group({ title: ['', [Validators.required, Validators.minLength(4), Validators.maxLength(24)] ], message: ['', [Validators.required, Validators.minLength(50), Validators.maxLength(1000)] ], }) }
การใช้ FormBuilder
มันค่อนข้างง่ายในการสร้างโครงสร้างแบบฟอร์มและรวบรัดกว่าในตัวอย่าง React ด้วยซ้ำ
get validationErrors() { const errors = {} Object.keys(this.postForm.controls).forEach(key => { errors[key] = '' const control = this.postForm.controls[key] if (control && !control.valid) { const messages = this.validationMessages[key] Object.keys(control.errors).forEach(error => { errors[key] += messages[error] + ' ' }) } }) return errors }
ในการรับข้อความตรวจสอบความถูกต้องแบบผูกมัดเราจำเป็นต้องดำเนินการบางอย่าง รหัสนี้นำมาจากเอกสารอย่างเป็นทางการโดยมีการเปลี่ยนแปลงเล็กน้อย โดยทั่วไปใน FormService ฟิลด์จะอ้างอิงเฉพาะข้อผิดพลาดที่ใช้งานอยู่ซึ่งระบุโดยชื่อตัวตรวจสอบความถูกต้องดังนั้นเราจำเป็นต้องจับคู่ข้อความที่ต้องการด้วยตนเองกับฟิลด์ที่ได้รับผลกระทบ นี่ไม่ใช่ข้อเสียเปรียบทั้งหมด ตัวอย่างเช่นยืมตัวเองไปสู่ความเป็นสากลได้ง่ายขึ้น
onSubmit({ value, valid }) { if (!valid) { return } this.formService.addPost(value) } onCancel() { this.router.navigate(['/posts']) } }
อีกครั้งเมื่อฟอร์มถูกต้องสามารถส่งข้อมูลไปยังการกลายพันธุ์ของ GraphQL ได้
วิธีโค้ดหุ่นยนต์
Create a new post
You are now posting as {{appService.username}}
{{validationErrors['title']}}
{{validationErrors['message']}}
Cancel Submit
สิ่งที่สำคัญที่สุดคือการอ้างอิง formGroup ที่เราสร้างขึ้นด้วย FormBuilder ซึ่งก็คือ [formGroup]='postForm'
มอบหมาย. เขตข้อมูลภายในฟอร์มถูกผูกไว้กับโมเดลฟอร์มผ่านทาง formControlName
ทรัพย์สิน. อีกครั้งเราปิดการใช้งานปุ่ม 'ส่ง' เมื่อแบบฟอร์มไม่ถูกต้อง นอกจากนี้เรายังต้องเพิ่มการตรวจสอบความสกปรกเนื่องจากที่นี่รูปแบบที่ไม่สกปรกยังคงไม่ถูกต้อง เราต้องการให้สถานะเริ่มต้นของปุ่มเป็น 'เปิดใช้งาน'
สรุป: แนวทางในการทำแบบฟอร์มใน React และ Angular นี้ค่อนข้างแตกต่างกันทั้งในด้านการตรวจสอบความถูกต้องและเทมเพลต วิธีเชิงมุมเกี่ยวข้องกับ 'เวทมนตร์' มากกว่าเล็กน้อยแทนที่จะเป็นการผูกแบบตรงไปตรงมา แต่ในทางกลับกันจะสมบูรณ์และละเอียดกว่า
อ้ออีกอย่าง การผลิตลดขนาดบันเดิล JS พร้อมการตั้งค่าเริ่มต้นจากตัวสร้างแอปพลิเคชัน: โดยเฉพาะอย่างยิ่ง Tree Shaking in React และการคอมไพล์ AOT ใน Angular
ไม่แปลกใจมากที่นี่ Angular เป็นสิ่งที่ใหญ่กว่าเสมอ
เมื่อใช้ gzip ขนาดจะลดลงเหลือ 275kb และ 127kb ตามลำดับ
โปรดทราบว่านี่คือไลบรารีของผู้ขายทั้งหมด จำนวนรหัสแอปพลิเคชันจริงมีเพียงเล็กน้อยเมื่อเปรียบเทียบซึ่งไม่ใช่ในกรณีของแอปพลิเคชันในโลกแห่งความเป็นจริง ที่นั่นอัตราส่วนน่าจะมากกว่า 1: 2 มากกว่า 1: 4 นอกจากนี้เมื่อคุณเริ่มรวมไลบรารีของบุคคลที่สามจำนวนมากด้วย React ขนาดของบันเดิลก็มีแนวโน้มที่จะเติบโตค่อนข้างเร็ว
ดูเหมือนว่าเราไม่สามารถ (อีกแล้ว!) ที่จะหาคำตอบที่ชัดเจนว่า Angular หรือ React นั้นดีกว่าสำหรับการพัฒนาเว็บ
ปรากฎว่าเวิร์กโฟลว์การพัฒนาใน React และ Angular อาจคล้ายกันมากขึ้นอยู่กับไลบรารีที่เราเลือกใช้ React ด้วย จากนั้นขึ้นอยู่กับความชอบส่วนบุคคลเป็นหลัก
หากคุณชอบสแต็คสำเร็จรูปการฉีดขึ้นรูปที่มีประสิทธิภาพและวางแผนที่จะใช้สินค้า RxJS บางอย่างให้เลือก Angular
หากคุณชอบคนจรจัดและสร้างสแต็กของคุณเองคุณชอบความตรงไปตรงมาของ JSX และชอบคุณสมบัติที่คำนวณได้ง่ายกว่าให้เลือก React / MobX
อีกครั้งคุณสามารถรับซอร์สโค้ดที่สมบูรณ์ของแอปพลิเคชันได้จากบทความนี้ ที่นี่ และ ที่นี่ .
หรือหากคุณต้องการที่ใหญ่กว่านี้ตัวอย่าง RealWorld:
การเขียนโปรแกรมด้วย React / MobX นั้นคล้ายกับ Angular มากกว่า React / Redux มีความแตกต่างที่น่าสังเกตในเทมเพลตและการจัดการการพึ่งพา แต่ก็มีเหมือนกัน ไม่แน่นอน / การผูกข้อมูล กระบวนทัศน์.
ตอบสนอง / Redux ด้วย ไม่เปลี่ยนรูป / ทิศทางเดียว กระบวนทัศน์เป็นสัตว์ร้ายที่แตกต่างกันอย่างสิ้นเชิง
อย่าหลงกลกับรอยเท้าเล็ก ๆ ของไลบรารี Redux อาจมีขนาดเล็ก แต่ก็เป็นกรอบ แนวทางปฏิบัติที่ดีที่สุดของ Redux ในปัจจุบันมุ่งเน้นไปที่การใช้ไลบรารีที่เข้ากันได้กับระบบ Redux เช่น Redux Saga สำหรับรหัส async และการดึงข้อมูล แบบฟอร์ม Redux สำหรับการจัดการแบบฟอร์ม เลือกใหม่ สำหรับตัวเลือกที่จำได้ (ค่าที่คำนวณของ Redux) และ จัดองค์ประกอบใหม่ เพื่อการจัดการวงจรชีวิตที่ละเอียดยิ่งขึ้น นอกจากนี้ยังมีการเปลี่ยนแปลงในชุมชน Redux จาก ไม่เปลี่ยนรูป js ถึง รามดา หรือ lodash / fp ซึ่งทำงานกับออบเจ็กต์ JS ธรรมดาแทนการแปลง
ตัวอย่างที่ดีของ Redux สมัยใหม่เป็นที่รู้จักกันดี React Boilerplate . มันเป็นกองพัฒนาที่น่ากลัว แต่ถ้าคุณดูมันมันแตกต่างจากที่เราเห็นในโพสต์นี้มากจริงๆ
ฉันรู้สึกว่า Angular ได้รับการปฏิบัติที่ไม่เป็นธรรมเล็กน้อยจากส่วนที่เป็นแกนนำของชุมชน JavaScript หลายคนที่แสดงความไม่พอใจกับเรื่องนี้อาจไม่พอใจกับการเปลี่ยนแปลงครั้งใหญ่ที่เกิดขึ้นระหว่าง AngularJS รุ่นเก่าและ Angular ในปัจจุบัน ในความคิดของฉันมันเป็นกรอบการทำงานที่สะอาดและมีประสิทธิผลมากที่จะพาโลกไปด้วยพายุหากเกิดขึ้นเมื่อ 1-2 ปีก่อนหน้านี้
อย่างไรก็ตาม Angular กำลังได้รับการตั้งหลักที่มั่นคงโดยเฉพาะอย่างยิ่งในโลกขององค์กรที่มีทีมงานขนาดใหญ่และความต้องการมาตรฐานและการสนับสนุนในระยะยาว หรือกล่าวอีกนัยหนึ่ง Angular เป็นวิธีที่วิศวกรของ Google คิดว่าการพัฒนาเว็บควรทำหากยังคงมีจำนวนมาก
สำหรับ MobX จะใช้การประเมินที่คล้ายกัน เยี่ยมมาก แต่ไม่ได้รับการชื่นชม
โดยสรุป: ก่อนที่จะเลือกระหว่าง React และ Angular ให้เลือกกระบวนทัศน์การเขียนโปรแกรมของคุณก่อน
ไม่แน่นอน / การผูกข้อมูล หรือ ไม่เปลี่ยนรูป / ทิศทางเดียว นั่น ... ดูเหมือนจะเป็นปัญหาที่แท้จริง
React เป็นไลบรารี JavaScript สำหรับสร้างส่วนต่อประสานผู้ใช้ เกี่ยวข้องกับมุมมองและช่วยให้คุณสามารถเลือกสถาปัตยกรรมส่วนหน้าที่เหลือของคุณได้ อย่างไรก็ตามระบบนิเวศของห้องสมุดที่แข็งแกร่งได้พัฒนาขึ้นรอบ ๆ ทำให้คุณสามารถสร้างเฟรมเวิร์กที่สมบูรณ์เกี่ยวกับ React ได้โดยการเพิ่มไลบรารีสองสามไลบรารีเข้าไป
กำหนดค่าทั้งหมดที่ใช้เป็นพื้นฐานสำหรับคุณสมบัติที่คำนวณเป็น BehaviorSubject (พร้อมใช้งานผ่าน RxJS) และสมัครสมาชิกด้วยตนเองสำหรับแต่ละเรื่องที่คุณสมบัตินั้นขึ้นอยู่กับ