วิธีโหลดรีดิวเซอร์แบบไดนามิกสำหรับการแยกรหัสในแอปพลิเคชัน Redux ได้อย่างไร


189

ฉันจะย้ายไปที่ Redux

แอปพลิเคชันของฉันประกอบด้วยชิ้นส่วนจำนวนมาก (หน้าส่วนประกอบ) ดังนั้นฉันต้องการสร้างตัวลดจำนวนมาก ตัวอย่างของ Redux แสดงว่าฉันควรใช้combineReducers()เพื่อสร้างตัวลด

เพราะฉันเข้าใจว่าแอปพลิเคชั่น Redux ควรมีร้านค้าหนึ่งแห่งและสร้างขึ้นเมื่อแอปพลิเคชันเริ่มต้นขึ้น เมื่อมีการสร้างร้านค้าฉันควรผ่านอุปกรณ์ลดแรงดันรวม วิธีนี้เหมาะสมถ้าแอปพลิเคชันไม่ใหญ่เกินไป

แต่ถ้าฉันสร้างมากกว่าหนึ่งบันเดิล JavaScript? ตัวอย่างเช่นแต่ละหน้าของแอปพลิเคชันมีมัดของตัวเอง ฉันคิดว่าในกรณีนี้ตัวลดที่รวมกันหนึ่งตัวไม่ดี ฉันตรวจสอบแหล่งที่มาของ Redux และพบreplaceReducer()ฟังก์ชั่น ดูเหมือนจะเป็นสิ่งที่ฉันต้องการ

ฉันสามารถสร้างตัวลดแบบรวมสำหรับแต่ละส่วนแอปพลิเคชันของฉันและใช้replaceReducer()เมื่อฉันย้ายระหว่างส่วนต่าง ๆ ของแอปพลิเคชัน

นี่เป็นวิธีที่ดีหรือไม่?

คำตอบ:


245

อัปเดต: ดูเพิ่มเติมว่า Twitter ดำเนินการอย่างไร

นี่ไม่ใช่คำตอบที่สมบูรณ์ แต่ควรช่วยคุณในการเริ่มต้น โปรดทราบว่าฉันไม่ได้ทิ้งตัวลดทอน - ฉันแค่เพิ่มตัวใหม่ลงในรายการชุดค่าผสม ฉันไม่เห็นเหตุผลใดที่จะทิ้งตัวลดขนาดเดิม - แม้แต่ในแอพที่ใหญ่ที่สุดที่คุณไม่น่าจะมีโมดูลแบบไดนามิกจำนวนมากซึ่งเป็นจุดที่คุณอาจต้องการตัดการเชื่อมต่อตัวลดขนาดบางอย่างในแอปพลิเคชันของคุณ

reducers.js

import { combineReducers } from 'redux';
import users from './reducers/users';
import posts from './reducers/posts';

export default function createReducer(asyncReducers) {
  return combineReducers({
    users,
    posts,
    ...asyncReducers
  });
}

store.js

import { createStore } from 'redux';
import createReducer from './reducers';

export default function configureStore(initialState) {
  const store = createStore(createReducer(), initialState);
  store.asyncReducers = {};
  return store;
}

export function injectAsyncReducer(store, name, asyncReducer) {
  store.asyncReducers[name] = asyncReducer;
  store.replaceReducer(createReducer(store.asyncReducers));
}

routes.js

import { injectAsyncReducer } from './store';

// Assuming React Router here but the principle is the same
// regardless of the library: make sure store is available
// when you want to require.ensure() your reducer so you can call
// injectAsyncReducer(store, name, reducer).

function createRoutes(store) {
  // ...

  const CommentsRoute = {
    // ...

    getComponents(location, callback) {
      require.ensure([
        './pages/Comments',
        './reducers/comments'
      ], function (require) {
        const Comments = require('./pages/Comments').default;
        const commentsReducer = require('./reducers/comments').default;

        injectAsyncReducer(store, 'comments', commentsReducer);
        callback(null, Comments);
      })
    }
  };

  // ...
}

อาจมีวิธีการแสดงที่สั้นกว่านี้ - ฉันแค่แสดงความคิด


13
ฉันชอบที่จะเห็นฟังก์ชั่นประเภทนี้ที่เพิ่มเข้ามาในโครงการ ความสามารถในการเพิ่ม reducers แบบไดนามิกเป็นสิ่งจำเป็นเมื่อจัดการกับการแยกรหัสและโปรแกรมขนาดใหญ่ ฉันมีต้นไม้ย่อยทั้งหมดที่ผู้ใช้บางคนอาจไม่สามารถเข้าถึงได้และการโหลดตัวลดทั้งหมดเป็นของเสีย ถึงแม้จะมีแอปพลิเคชั่นขนาดใหญ่ที่ไม่สนใจการรีดักซ์ก็สามารถลดทอนได้
JeffBaumgardt

2
บางครั้งมันเป็นของเสียที่ใหญ่กว่าในการ 'เพิ่มประสิทธิภาพ' สิ่งที่ไม่เหมาะสม
XML

1
หวังว่าความคิดเห็นข้างต้นสมเหตุสมผล ... ขณะที่ฉันวิ่งออกจากห้อง แต่โดยทั่วไปฉันไม่เห็นวิธีที่ง่ายในการรวมเอา reducers เข้าไว้ในกิ่งเดียวบนต้นไม้สถานะของเราเมื่อพวกมันถูกโหลดแบบไดนามิกจากเส้นทางที่แตกต่างกัน/homepageและจากนั้นสาขานั้นจะถูกโหลดเมื่อผู้ใช้ไปถึงprofile.ตัวอย่าง การทำเช่นนี้จะน่ากลัว มิฉะนั้นฉันจะมีช่วงเวลาที่ยากออกมาจากต้นไม้รัฐของฉันหรือฉันจะต้องมีชื่อสาขาที่เฉพาะเจาะจงมากuser-permissionsและuser-personal
BryceHayden

1
และฉันควรทำอย่างไรถ้าฉันมีสถานะเริ่มต้น?
Stalso

3
github.com/mxstbr/react-boilerplate boilerplate ใช้เทคนิคเดียวกันกับที่ระบุไว้ที่นี่เพื่อโหลด reducers
Pouya Sanooei

25

นี่คือวิธีที่ฉันใช้มันในแอปปัจจุบัน (อิงตามรหัสโดย Dan จากปัญหา GitHub!)

// Based on https://github.com/rackt/redux/issues/37#issue-85098222
class ReducerRegistry {
  constructor(initialReducers = {}) {
    this._reducers = {...initialReducers}
    this._emitChange = null
  }
  register(newReducers) {
    this._reducers = {...this._reducers, ...newReducers}
    if (this._emitChange != null) {
      this._emitChange(this.getReducers())
    }
  }
  getReducers() {
    return {...this._reducers}
  }
  setChangeListener(listener) {
    if (this._emitChange != null) {
      throw new Error('Can only set the listener for a ReducerRegistry once.')
    }
    this._emitChange = listener
  }
}

สร้างอินสแตนซ์รีจิสตรีเมื่อบู๊ตแอปของคุณส่งผ่าน reducers ซึ่งจะรวมอยู่ในบันเดิลรายการ:

// coreReducers is a {name: function} Object
var coreReducers = require('./reducers/core')
var reducerRegistry = new ReducerRegistry(coreReducers)

จากนั้นเมื่อกำหนดค่าที่เก็บและเส้นทางให้ใช้ฟังก์ชั่นที่คุณสามารถมอบรีจิสตรีให้กับ:

var routes = createRoutes(reducerRegistry)
var store = createStore(reducerRegistry)

ฟังก์ชันเหล่านี้มีหน้าตาเป็นอย่างไร:

function createRoutes(reducerRegistry) {
  return <Route path="/" component={App}>
    <Route path="core" component={Core}/>
    <Route path="async" getComponent={(location, cb) => {
      require.ensure([], require => {
        reducerRegistry.register({async: require('./reducers/async')})
        cb(null, require('./screens/Async'))
      })
    }}/>
  </Route>
}

function createStore(reducerRegistry) {
  var rootReducer = createReducer(reducerRegistry.getReducers())
  var store = createStore(rootReducer)

  reducerRegistry.setChangeListener((reducers) => {
    store.replaceReducer(createReducer(reducers))
  })

  return store
}

นี่คือตัวอย่างสดพื้นฐานซึ่งสร้างขึ้นด้วยการตั้งค่านี้และแหล่งที่มา:

นอกจากนี้ยังครอบคลุมถึงการกำหนดค่าที่จำเป็นเพื่อเปิดใช้งานการโหลดซ้ำแบบร้อนสำหรับอุปกรณ์ลดแรงดันทั้งหมดของคุณ


ขอบคุณ @ จอนนี่เพียงแค่หัวตัวอย่างคือการโยนข้อผิดพลาดในขณะนี้
เจสันเจนาธา

ประกาศ createReducer () หายไปจากคำตอบของคุณ (ฉันรู้ว่ามันอยู่ในคำตอบของ Dan Abrahamov แต่ฉันคิดว่าการรวมมันจะหลีกเลี่ยงความสับสน)
Packet Tracer

6

ขณะนี้มีโมดูลที่เพิ่ม reducers แบบฉีดเข้าไปในที่เก็บ redux มันถูกเรียกว่าRedux หัวฉีด

นี่คือวิธีการใช้งาน:

  1. อย่ารวม reducers แทนที่จะวางไว้ในวัตถุ (ซ้อน) ของฟังก์ชั่นตามปกติคุณจะได้ แต่ไม่รวมพวกเขา

  2. ใช้ createInjectStore จาก redux-injector แทน createStore จาก redux

  3. ฉีด reducers ใหม่ด้วย injectReducer

นี่คือตัวอย่าง:

import { createInjectStore, injectReducer } from 'redux-injector';

const reducersObject = {
   router: routerReducerFunction,
   data: {
     user: userReducerFunction,
     auth: {
       loggedIn: loggedInReducerFunction,
       loggedOut: loggedOutReducerFunction
     },
     info: infoReducerFunction
   }
 };

const initialState = {};

let store = createInjectStore(
  reducersObject,
  initialState
);

// Now you can inject reducers anywhere in the tree.
injectReducer('data.form', formReducerFunction);

การเปิดเผยแบบเต็ม: ฉันเป็นผู้สร้างโมดูล


4

ณ วันที่ตุลาคม 2017:

  • Reedux

    ใช้สิ่งที่แดนแนะนำและไม่มีอะไรเพิ่มเติมโดยไม่ต้องแตะที่ร้านค้าโครงการหรือนิสัยของคุณ

มีห้องสมุดอื่นด้วย แต่อาจมีการพึ่งพามากเกินไปตัวอย่างน้อยการใช้งานที่ซับซ้อนไม่เข้ากันกับมิดเดิลแวร์บางตัวหรือต้องการให้คุณเขียนการจัดการสถานะของคุณใหม่ คัดลอกมาจากหน้าแนะนำของ Reedux:


2

เราเปิดตัวไลบรารี่ใหม่ที่ช่วยปรับแอป Redux และอนุญาตให้เพิ่ม / ลบ Reducers และมิดเดิลแวร์แบบไดนามิก

โปรดดูที่ https://github.com/Microsoft/redux-dynamic-modules

โมดูลมีประโยชน์ดังต่อไปนี้:

  • สามารถนำโมดูลกลับมาใช้ใหม่ได้อย่างง่ายดายในแอปพลิเคชันหรือระหว่างแอปพลิเคชันที่คล้ายกันหลายรายการ

  • ส่วนประกอบประกาศโมดูลที่ต้องการโดยพวกเขาและโมดูล redux-dynamic ทำให้มั่นใจได้ว่าโมดูลจะถูกโหลดสำหรับส่วนประกอบ

  • โมดูลสามารถเพิ่ม / ลบออกจากร้านค้าแบบไดนามิกเช่น เมื่อส่วนประกอบประกอบหรือเมื่อผู้ใช้ทำการกระทำ

คุณสมบัติ

  • จัดกลุ่มตัวลด, มิดเดิลแวร์และรวมเข้าด้วยกันเป็นโมดูลเดียวที่สามารถใช้ซ้ำได้
  • เพิ่มและลบโมดูลจากที่เก็บ Redux ได้ตลอดเวลา
  • ใช้องค์ประกอบที่รวมอยู่เพื่อเพิ่มโมดูลโดยอัตโนมัติเมื่อมีการแสดงส่วนประกอบ
  • ส่วนขยายให้การรวมเข้ากับไลบรารียอดนิยมรวมถึง redux-saga และ redux-observable

ตัวอย่างสถานการณ์

  • คุณไม่ต้องการโหลดรหัสสำหรับตัวลดขนาดหน้าทั้งหมด กำหนดโมดูลสำหรับตัวลดบางตัวและใช้ DynamicModuleLoader และไลบรารีเช่นสามารถโหลดได้เพื่อดาวน์โหลดและเพิ่มโมดูลของคุณในขณะทำงาน
  • คุณมี reducers / middleware ทั่วไปที่จำเป็นต้องใช้ซ้ำในพื้นที่ต่าง ๆ ของแอปพลิเคชันของคุณ กำหนดโมดูลและรวมไว้ในพื้นที่เหล่านั้นได้อย่างง่ายดาย
  • คุณมี mono-repo ที่มีหลายแอพพลิเคชั่นซึ่งมีสถานะคล้ายกัน สร้างแพ็คเกจที่มีบางโมดูลและนำกลับมาใช้ใหม่ในแอปพลิเคชันของคุณ

0

นี่เป็นอีกตัวอย่างหนึ่งที่มีการแยกรหัสและร้านค้า redux ค่อนข้างเรียบง่ายและสง่างามในความคิดของฉัน ฉันคิดว่ามันอาจจะมีประโยชน์มากสำหรับผู้ที่กำลังมองหาวิธีแก้ปัญหาการทำงาน

นี้เก็บเป็นบิตง่ายก็ไม่ได้บังคับให้คุณมี namespace (reducer.name) ในวัตถุรัฐของคุณแน่นอนอาจจะมีการปะทะกันกับชื่อ แต่คุณสามารถควบคุมโดยการสร้างการประชุมตั้งชื่อสำหรับ reducers ของคุณและมัน ควรจะดี

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.