จะเขียนไปที่ localStorage ในแอป Redux ได้อย่างไร


คำตอบ:


224

ตัวลดไม่เคยเป็นสถานที่ที่เหมาะสมในการทำเช่นนี้เพราะตัวลดควรบริสุทธิ์และไม่มีผลข้างเคียง

ฉันอยากจะแนะนำให้ทำในสมาชิก:

store.subscribe(() => {
  // persist your state
})

ก่อนที่จะสร้างร้านค้าอ่านส่วนที่ยังคงมีอยู่:

const persistedState = // ...
const store = createStore(reducer, persistedState)

หากคุณใช้combineReducers()คุณจะสังเกตเห็นว่าตัวลดที่ไม่ได้รับสถานะจะ“ บู๊ต” ตามปกติโดยใช้ค่าstateอาร์กิวเมนต์เริ่มต้น มันมีประโยชน์ทีเดียว

ขอแนะนำให้คุณ debounce สมาชิกของคุณเพื่อที่คุณจะได้ไม่เขียนไปยัง localStorage เร็วเกินไปหรือคุณจะประสบปัญหาด้านประสิทธิภาพ

ท้ายที่สุดคุณสามารถสร้างมิดเดิลแวร์ที่ห่อหุ้มสิ่งนั้นเป็นทางเลือก แต่ฉันจะเริ่มต้นด้วยผู้สมัครสมาชิกเพราะมันเป็นวิธีที่ง่ายกว่าและทำงานได้ดี


1
จะทำอย่างไรหากฉันต้องการสมัครเป็นส่วนหนึ่งของรัฐ? เป็นไปได้ไหม ฉันไปข้างหน้าด้วยบางสิ่งที่แตกต่างกันเล็กน้อย: 1. บันทึกสถานะที่คงอยู่นอกการเปลี่ยนค่า 2. โหลดสถานะที่คงอยู่ภายในตัวสร้างปฏิกิริยา (หรือกับ componentWillMount) พร้อมกับการดำเนินการ redux 2 ดีทั้งหมดแทนที่จะโหลดข้อมูลที่เก็บไว้ในร้านโดยตรงหรือไม่ (ซึ่งฉันพยายามแยกต่างหากสำหรับ SSR) ขอบคุณสำหรับ Redux! มันยอดเยี่ยมมากฉันรักมันหลังจากหลงทางด้วยรหัสโครงการขนาดใหญ่ของฉันตอนนี้มันกลับเป็นเรื่องง่ายและคาดเดาได้แล้ว :)
Mark Uretsky

ภายใน store.subscribe โทรกลับคุณมีการเข้าถึงแบบเต็มไปเก็บข้อมูลในปัจจุบันเพื่อให้คุณสามารถยังคงมีอยู่ส่วนหนึ่งส่วนใดที่คุณมีความสนใจใน
บ่อเฉิน

35
Dan Abramov ทำวิดีโอทั้งหมดด้วยเช่น: egghead.io/lessons/…
NateW

2
มีความกังวลเกี่ยวกับประสิทธิภาพของการทำงานที่ถูกต้องตามกฎหมายในการอัปเดตสถานะทุกครั้งหรือไม่ เบราว์เซอร์เป็นอนุกรมบนเธรดแยกกันหรือไม่
สตีเฟ่นพอล

7
สิ่งนี้ดูสิ้นเปลือง OP กล่าวว่ามีเพียงส่วนหนึ่งของรัฐที่จะต้องคงอยู่ สมมติว่าคุณมีร้านค้าที่มี 100 คีย์ที่แตกต่างกัน คุณต้องการที่จะยืนยัน 3 ข้อที่เปลี่ยนแปลงไม่บ่อย ตอนนี้คุณกำลังแยกวิเคราะห์และอัปเดตร้านค้าในท้องถิ่นในทุก ๆ การเปลี่ยนแปลงเล็ก ๆ น้อย ๆ ของ 100 คีย์ของคุณทั้งหมดในขณะที่ไม่มีคีย์ 3 ตัวที่คุณสนใจจะยังคงมีการเปลี่ยนแปลง โซลูชันโดย @Gardezi ด้านล่างเป็นวิธีที่ดีกว่าเนื่องจากคุณสามารถเพิ่มตัวรับฟังเหตุการณ์ในมิดเดิลแวร์ของคุณเพื่อให้คุณอัปเดตเฉพาะเมื่อคุณต้องการเช่นcodepen.io/retrcult/pen/qNRzKN
Anna T

153

ในการเติมช่องว่างของคำตอบของ Dan Abramov คุณสามารถใช้store.subscribe()สิ่งนี้:

store.subscribe(()=>{
  localStorage.setItem('reduxState', JSON.stringify(store.getState()))
})

ก่อนที่จะสร้างร้านค้าให้ตรวจสอบlocalStorageและแยก JSON ใด ๆ ภายใต้คีย์ของคุณดังนี้:

const persistedState = localStorage.getItem('reduxState') 
                       ? JSON.parse(localStorage.getItem('reduxState'))
                       : {}

จากนั้นส่งpersistedStateค่าคงที่นี้ไปยังcreateStoreวิธีการของคุณดังนี้:

const store = createStore(
  reducer, 
  persistedState,
  /* any middleware... */
)

6
ง่ายและมีประสิทธิภาพโดยไม่ต้องพึ่งพาพิเศษ
AxeEffect

1
สร้างตัวกลางอาจจะดูดีขึ้นเล็กน้อย แต่ผมเห็นว่ามันเป็นที่มีประสิทธิภาพและเพียงพอที่จะกรณีส่วนใหญ่
บ่อเฉิน

มีวิธีที่ดีในการทำเช่นนี้เมื่อใช้ mixReducers และคุณต้องการเพียงเก็บหนึ่งร้าน?
brendangibson

1
หากไม่มีสิ่งใดใน localStorage ควรpersistedStateส่งคืนinitialStateแทนที่จะเป็นวัตถุเปล่า? มิฉะนั้นฉันคิดว่าcreateStoreจะเริ่มต้นด้วยวัตถุที่ว่างเปล่า
อเล็กซ์

1
@Alex คุณพูดถูกนอกเสียจากว่า initialState ของคุณจะว่างเปล่า
ลิงก์ 14

47

ในคำ: มิดเดิลแวร์

ตรวจสอบRedux-ยังคงมีอยู่ หรือเขียนของคุณเอง

[อัปเดต 18 ธันวาคม 2559] แก้ไขเพื่อลบการกล่าวถึงโครงการที่คล้ายกันสองโครงการที่ไม่ใช้งานหรือเลิกใช้แล้ว


12

หากใครมีปัญหากับวิธีแก้ไขปัญหาข้างต้นคุณสามารถเขียนของคุณเองเพื่อ ให้ฉันแสดงสิ่งที่ฉันทำ ไม่สนใจsaga middlewareสิ่งต่าง ๆ เพียงมุ่งเน้นสองสิ่งlocalStorageMiddlewareและreHydrateStoreวิธี localStorageMiddlewareดึงทั้งหมดredux stateและทำให้มันอยู่ในlocal storageและrehydrateStoreดึงทั้งหมดapplicationStateในการจัดเก็บในท้องถิ่นถ้าปัจจุบันและทำให้มันอยู่ในredux store

import {createStore, applyMiddleware} from 'redux'
import createSagaMiddleware from 'redux-saga';
import decoristReducers from '../reducers/decorist_reducer'

import sagas from '../sagas/sagas';

const sagaMiddleware = createSagaMiddleware();

/**
 * Add all the state in local storage
 * @param getState
 * @returns {function(*): function(*=)}
 */
const localStorageMiddleware = ({getState}) => { // <--- FOCUS HERE
    return (next) => (action) => {
        const result = next(action);
        localStorage.setItem('applicationState', JSON.stringify(
            getState()
        ));
        return result;
    };
};


const reHydrateStore = () => { // <-- FOCUS HERE

    if (localStorage.getItem('applicationState') !== null) {
        return JSON.parse(localStorage.getItem('applicationState')) // re-hydrate the store

    }
}


const store = createStore(
    decoristReducers,
    reHydrateStore(),// <-- FOCUS HERE
    applyMiddleware(
        sagaMiddleware,
        localStorageMiddleware,// <-- FOCUS HERE 
    )
)

sagaMiddleware.run(sagas);

export default store;

2
สวัสดีนี่จะไม่ส่งผลให้มีการเขียนมากมายถึงlocalStorageแม้ว่าจะไม่มีการเปลี่ยนแปลงอะไรในสโตร์ คุณชดเชยการเขียนที่ไม่จำเป็นได้อย่างไร
user566245

มันใช้งานได้ดียังไงดี คำถาม: เกิดอะไรขึ้นในกรณีที่เรามีข้อมูลหลายตัวเป็นตัวลดหลายตัวที่มีข้อมูลขนาดใหญ่
Kunvar Singh

3

ฉันไม่สามารถตอบ @Gardezi แต่ตัวเลือกตามรหัสของเขาอาจเป็น:

const rootReducer = combineReducers({
    users: authReducer,
});

const localStorageMiddleware = ({ getState }) => {
    return next => action => {
        const result = next(action);
        if ([ ACTIONS.LOGIN ].includes(result.type)) {
            localStorage.setItem(appConstants.APP_STATE, JSON.stringify(getState()))
        }
        return result;
    };
};

const reHydrateStore = () => {
    const data = localStorage.getItem(appConstants.APP_STATE);
    if (data) {
        return JSON.parse(data);
    }
    return undefined;
};

return createStore(
    rootReducer,
    reHydrateStore(),
    applyMiddleware(
        thunk,
        localStorageMiddleware
    )
);

ความแตกต่างคือเราเพิ่งบันทึกการกระทำบางอย่างคุณสามารถใช้ฟังก์ชัน debounce เพื่อบันทึกการโต้ตอบล่าสุดของสถานะของคุณเท่านั้น


1

ฉันช้าไปหน่อย แต่ฉันได้ติดตั้งสถานะถาวรตามตัวอย่างที่ระบุไว้ที่นี่ หากคุณต้องการอัปเดตสถานะทุก ๆ X วินาทีวิธีการนี้อาจช่วยคุณได้:

  1. กำหนดฟังก์ชั่นเสื้อคลุม

    let oldTimeStamp = (Date.now()).valueOf()
    const millisecondsBetween = 5000 // Each X milliseconds
    function updateLocalStorage(newState)
    {
        if(((Date.now()).valueOf() - oldTimeStamp) > millisecondsBetween)
        {
            saveStateToLocalStorage(newState)
            oldTimeStamp = (Date.now()).valueOf()
            console.log("Updated!")
        }
    }
    
  2. เรียกใช้ฟังก์ชัน wrapper ในสมาชิกของคุณ

        store.subscribe((state) =>
        {
        updateLocalStorage(store.getState())
         });
    

ในตัวอย่างนี้สถานะจะได้รับการอัปเดตไม่เกิน 5 วินาทีโดยไม่คำนึงว่าจะมีการเรียกใช้การอัปเดตบ่อยเพียงใด


1
คุณสามารถห่อ(state) => { updateLocalStorage(store.getState()) }ในlodash.throttleลักษณะนี้store.subscribe(throttle(() => {(state) => { updateLocalStorage(store.getState())} }และลบเวลาตรวจสอบตรรกะภายใน
GeorgeMA

1

คำแนะนำที่ยอดเยี่ยมและข้อความที่ตัดตอนมาจากคำตอบอื่น ๆ (และบทความระดับกลางของ Jam Creencia ) นี่เป็นคำตอบที่สมบูรณ์!

เราต้องการไฟล์ที่มี 2 ฟังก์ชั่นที่บันทึก / โหลดสถานะไปยัง / จากที่จัดเก็บในตัวเครื่อง:

// FILE: src/common/localStorage/localStorage.js

// Pass in Redux store's state to save it to the user's browser local storage
export const saveState = (state) =>
{
  try
  {
    const serializedState = JSON.stringify(state);
    localStorage.setItem('state', serializedState);
  }
  catch
  {
    // We'll just ignore write errors
  }
};



// Loads the state and returns an object that can be provided as the
// preloadedState parameter of store.js's call to configureStore
export const loadState = () =>
{
  try
  {
    const serializedState = localStorage.getItem('state');
    if (serializedState === null)
    {
      return undefined;
    }
    return JSON.parse(serializedState);
  }
  catch (error)
  {
    return undefined;
  }
};

ฟังก์ชั่นเหล่านั้นนำเข้าโดยstore.jsที่เรากำหนดค่าร้านค้าของเรา:

หมายเหตุ: คุณจะต้องเพิ่มการอ้างอิงหนึ่งรายการ: npm install lodash.throttle

// FILE: src/app/redux/store.js

import { configureStore, applyMiddleware } from '@reduxjs/toolkit'

import throttle from 'lodash.throttle';

import rootReducer from "./rootReducer";
import middleware from './middleware';

import { saveState, loadState } from 'common/localStorage/localStorage';


// By providing a preloaded state (loaded from local storage), we can persist
// the state across the user's visits to the web app.
//
// READ: https://redux.js.org/recipes/configuring-your-store
const store = configureStore({
	reducer: rootReducer,
	middleware: middleware,
	enhancer: applyMiddleware(...middleware),
	preloadedState: loadState()
})


// We'll subscribe to state changes, saving the store's state to the browser's
// local storage. We'll throttle this to prevent excessive work.
store.subscribe(
	throttle( () => saveState(store.getState()), 1000)
);


export default store;

ร้านค้าจะนำเข้าสู่index.jsเพื่อให้สามารถส่งผ่านไปยังผู้ให้บริการที่ล้อมApp.js :

// FILE: src/index.js

import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'

import App from './app/core/App'

import store from './app/redux/store';


// Provider makes the Redux store available to any nested components
render(
	<Provider store={store}>
		<App />
	</Provider>,
	document.getElementById('root')
)

โปรดทราบว่าการนำเข้าแบบสัมบูรณ์จำเป็นต้องมีการเปลี่ยนแปลงเป็นYourProjectFolder / jsconfig.jsonซึ่งเป็นการบอกว่าจะค้นหาไฟล์ได้ที่ไหนหากไม่สามารถค้นหาได้ในตอนแรก มิฉะนั้นคุณจะเห็นข้อร้องเรียนเกี่ยวกับความพยายามที่จะมีอะไรบางอย่างที่นำเข้าจากนอกsrc

{
  "compilerOptions": {
    "baseUrl": "src"
  },
  "include": ["src"]
}

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