Viết thư cho localStorage ở đâu trong ứng dụng Redux?


166

Tôi muốn duy trì một số phần của cây trạng thái của mình cho localStorage. Nơi thích hợp để làm như vậy là gì? Giảm hay hành động?

Câu trả lời:


223

Reducer không bao giờ là một nơi thích hợp để làm điều này bởi vì các chất làm giảm nên tinh khiết và không có tác dụng phụ.

Tôi khuyên bạn chỉ nên làm điều đó trong một thuê bao:

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

Trước khi tạo cửa hàng, hãy đọc những phần vẫn tồn tại:

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

Nếu bạn sử dụng, combineReducers()bạn sẽ nhận thấy rằng các bộ giảm tốc chưa nhận được trạng thái sẽ khởi động lại như bình thường bằng cách sử dụng stategiá trị đối số mặc định của chúng . Điều này có thể khá tiện dụng.

Bạn nên gỡ bỏ thuê bao của mình để không viết thư cho localStorage quá nhanh, hoặc bạn sẽ gặp vấn đề về hiệu suất.

Cuối cùng, bạn có thể tạo một phần mềm trung gian đóng gói nó như một giải pháp thay thế, nhưng tôi bắt đầu với một người đăng ký vì đó là một giải pháp đơn giản hơn và thực hiện công việc tốt.


1
Điều gì nếu tôi muốn đăng ký chỉ là một phần của tiểu bang? điều đó có thể không? Tôi đã đi trước với một cái gì đó hơi khác một chút: 1. lưu trạng thái bền bỉ bên ngoài redux. 2. tải trạng thái bền vững bên trong hàm tạo phản ứng (hoặc với thành phầnWillMount) với hành động redux. Là 2 là hoàn toàn tốt thay vì tải trực tiếp dữ liệu trên cửa hàng? (mà tôi đang cố giữ riêng cho SSR). Nhân tiện cảm ơn Redux! Thật tuyệt vời, tôi yêu nó, sau khi bị lạc với mã dự án lớn của tôi bây giờ tất cả trở lại đơn giản và có thể dự đoán được :)
Mark Uretsky

trong store.subscribe gọi lại, bạn có toàn quyền truy cập vào dữ liệu cửa hàng hiện tại, do đó bạn có thể duy trì bất kỳ phần nào bạn quan tâm
Bo Chen

35
Dan Abramov cũng đã thực hiện một video toàn bộ về nó: egghead.io/lessons/ Kẻ
NateW

2
Có mối quan tâm hiệu suất hợp pháp với việc tuần tự hóa trạng thái trên mỗi bản cập nhật cửa hàng? Có trình duyệt nối tiếp trên một chủ đề riêng biệt có thể?
Stephen Paul

7
Điều này có vẻ lãng phí. OP đã nói rằng chỉ một phần của nhà nước cần phải được duy trì. Giả sử bạn có một cửa hàng với 100 khóa khác nhau. Bạn chỉ muốn duy trì 3 trong số chúng được thay đổi không thường xuyên. Bây giờ bạn đang phân tích cú pháp và cập nhật cửa hàng địa phương trên mỗi thay đổi nhỏ cho bất kỳ 100 khóa nào của bạn, trong khi không có bất kỳ 3 khóa nào bạn quan tâm vẫn tồn tại thậm chí đã được thay đổi. Giải pháp của @Gardezi dưới đây là cách tiếp cận tốt hơn vì bạn có thể thêm trình lắng nghe sự kiện vào phần mềm trung gian để bạn chỉ cập nhật khi bạn thực sự cần ví dụ: codepen.io/retrcult/pen/qNRzKN
Anna T

153

Để điền vào chỗ trống trong câu trả lời của Dan Abramov, bạn có thể sử dụng store.subscribe()như thế này:

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

Trước khi tạo cửa hàng, hãy kiểm tra localStoragevà phân tích bất kỳ JSON nào dưới khóa của bạn như thế này:

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

Sau đó, bạn truyền persistedStatehằng số này cho createStorephương thức của bạn như thế này:

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

6
Đơn giản và hiệu quả, không phụ thuộc thêm.
AxeEffect

1
tạo một phần mềm trung gian có thể trông tốt hơn một chút, nhưng tôi đồng ý rằng nó hiệu quả và đủ cho hầu hết các trường hợp
Bo Chen

Có một cách hay để làm điều này khi sử dụng kết hợp, và bạn chỉ muốn duy trì một cửa hàng?
brendangibson

1
Nếu không có gì trong localStorage, có nên persistedStatetrả về initialStatethay vì một đối tượng trống không? Nếu không tôi nghĩ createStoresẽ khởi tạo với đối tượng trống rỗng đó.
Alex

1
@Alex Bạn nói đúng, trừ khi initState của bạn trống.
Link14 ngày

47

Trong một từ: phần mềm trung gian.

Kiểm tra redux-kiên trì . Hoặc viết cho riêng bạn.

[CẬP NHẬT ngày 18 tháng 12 năm 2016] Đã chỉnh sửa để xóa đề cập đến hai dự án tương tự hiện không hoạt động hoặc không dùng nữa.


12

Nếu bất cứ ai đang có bất kỳ vấn đề với các giải pháp trên, bạn có thể viết riêng của bạn. Hãy để tôi chỉ cho bạn những gì tôi đã làm. Bỏ qua saga middlewaremọi thứ chỉ tập trung vào hai điều localStorageMiddlewarereHydrateStorephương pháp. các localStorageMiddlewarekéo tất cả các redux statevà đặt nó trong local storagerehydrateStorekéo tất cả các applicationStatetrong lưu trữ địa phương nếu hiện tại và đặt nó trongredux 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
Xin chào, điều này sẽ không dẫn đến việc viết nhiều localStoragengay cả khi không có gì trong cửa hàng thay đổi? Cách bạn bù đắp cho việc ghi không cần thiết
user566245

Vâng, nó hoạt động, dù sao nó là ý tưởng tốt để có? Câu hỏi: Điều gì xảy ra trong trường hợp chúng ta có nhiều dữ liệu menas nhiều bộ giảm với dữ liệu lớn?
Kunvar Singh

3

Tôi không thể trả lời @Gardezi nhưng một tùy chọn dựa trên mã của anh ấy có thể là:

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
    )
);

sự khác biệt là chúng tôi chỉ lưu một số hành động, bạn có thể sử dụng chức năng gỡ lỗi để chỉ lưu tương tác cuối cùng của trạng thái của bạn


1

Tôi hơi muộn nhưng tôi đã thực hiện một trạng thái liên tục theo các ví dụ được nêu ở đây. Nếu bạn muốn cập nhật trạng thái chỉ sau mỗi X giây, phương pháp này có thể giúp bạn:

  1. Xác định chức năng bao bọc

    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. Gọi một chức năng bao bọc trong thuê bao của bạn

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

Trong ví dụ này, trạng thái được cập nhật tối đa cứ sau 5 giây, bất kể tần suất cập nhật được kích hoạt.


1
Bạn có thể bọc (state) => { updateLocalStorage(store.getState()) }trong lodash.throttlenhư thế này: store.subscribe(throttle(() => {(state) => { updateLocalStorage(store.getState())} }và loại bỏ thời gian kiểm tra bên trong logic.
GeorgeMA

1

Dựa trên các đề xuất tuyệt vời và đoạn trích mã ngắn được cung cấp trong các câu trả lời khác (và bài viết Trung bình của Jam Creencia ), đây là một giải pháp hoàn chỉnh!

Chúng tôi cần một tệp chứa 2 hàm lưu / tải trạng thái đến / từ bộ nhớ cục bộ:

// 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;
  }
};

Các hàm này được nhập bởi store.js nơi chúng tôi định cấu hình cửa hàng của chúng tôi:

LƯU Ý: Bạn sẽ cần thêm một phụ thuộc: 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;

Cửa hàng được nhập vào index.js để có thể chuyển nó vào Nhà cung cấp bao bọc 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')
)

Lưu ý rằng nhập tuyệt đối yêu cầu thay đổi này đối với YourProjectFolder / jsconfig.json - điều này cho biết nơi tìm tệp nếu nó không thể tìm thấy chúng lúc đầu. Mặt khác, bạn sẽ thấy các khiếu nại về việc cố gắng nhập nội dung nào đó từ bên ngoài src .

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.