วิธีที่ดีที่สุดในการเข้าถึงที่เก็บ redux นอกองค์ประกอบปฏิกิริยาคืออะไร?


192

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

นี่เป็นของฉัน api.js

// tooling modules
import axios from 'axios'

// configuration
const api = axios.create()
api.defaults.baseURL = 'http://localhost:5001/api/v1'
api.defaults.headers.common['Authorization'] = 'AUTH_TOKEN' // need the token here
api.defaults.headers.post['Content-Type'] = 'application/json'

export default api

ตอนนี้ฉันต้องการเข้าถึงจุดข้อมูลจากร้านค้าของฉันนี่คือสิ่งที่จะดูเหมือนถ้าฉันพยายามดึงข้อมูลภายในส่วนประกอบที่ตอบสนองโดยใช้ @connect

// connect to store
@connect((store) => {
  return {
    auth: store.auth
  }
})
export default class App extends Component {
  componentWillMount() {
    // this is how I would get it in my react component
    console.log(this.props.auth.tokens.authorization_token) 
  }
  render() {...}
}

มีข้อมูลเชิงลึกหรือรูปแบบเวิร์กโฟลว์ใด ๆ


คุณไม่ต้องการให้อินสแตนซ์ Axios ของคุณอาศัยอยู่ใน redux middleware หรือไม่ แอปพลิเคชั่นของคุณจะสามารถใช้งานได้ด้วยวิธีนี้
Emrys Myrooin

คุณสามารถนำเข้า apiในAppคลาสและหลังจากได้รับโทเค็นการอนุญาตที่คุณสามารถทำได้api.defaults.headers.common['Authorization'] = this.props.auth.tokens.authorization_token;และในเวลาเดียวกันคุณสามารถเก็บไว้ใน localStorage ได้เช่นกันดังนั้นเมื่อผู้ใช้รีเฟรชหน้าเพจคุณสามารถตรวจสอบว่ามีโทเค็นอยู่ใน localStorage หรือไม่ ใช่คุณสามารถตั้งค่าได้ฉันคิดว่าจะเป็นการดีที่สุดที่จะตั้งค่าโทเค็นในโมดูล api ทันทีที่คุณได้รับ
Dhruv Kumar Jha

1
Dan Abromov ให้ตัวอย่างในคิวปัญหาที่นี่: github.com/reactjs/redux/issues/916
chrisjlee

1
หากคุณต้องการเข้าถึงสถานะเฉพาะจากตัวลดเฉพาะคุณสามารถลองใช้Redux-named-reducers ที่อนุญาตให้คุณเข้าถึงสถานะล่าสุดได้จากทุกที่
miles_christian

คำตอบ:


146

ส่งออกร้านค้าจากโมดูลที่คุณเรียกว่า createStoreด้วย จากนั้นคุณจะมั่นใจได้ว่าทั้งสองจะถูกสร้างขึ้นและจะไม่ก่อให้เกิดมลพิษพื้นที่หน้าต่างทั่วโลก

MyStore.js

const store = createStore(myReducer);
export store;

หรือ

const store = createStore(myReducer);
export default store;

MyClient.js

import {store} from './MyStore'
store.dispatch(...)

หรือถ้าคุณใช้ ค่าเริ่มต้น

import store from './MyStore'
store.dispatch(...)

สำหรับกรณีการใช้งานหลายร้าน

หากคุณต้องการร้านค้าหลายอินสแตนซ์ให้ส่งออกฟังก์ชั่นจากโรงงาน ฉันอยากจะแนะนำให้มันasync(กลับมาpromise)

async function getUserStore (userId) {
   // check if user store exists and return or create it.
}
export getUserStore

บนไคลเอนต์ (ในasyncบล็อก)

import {getUserStore} from './store'

const joeStore = await getUserStore('joe')

47
คำเตือนสำหรับแอพพลิเคชั่น isomorphic: การใช้ฝั่งเซิร์ฟเวอร์นี้จะแชร์ที่เก็บ redux กับผู้ใช้ทั้งหมดของคุณ !!!
Lawrence Wagerfield

6
คำถามนี้ยังไม่ได้ระบุอย่างชัดเจนว่า "เบราว์เซอร์" เนื่องจากสามารถใช้ทั้ง Redux และ JavaScript บนเซิร์ฟเวอร์หรือเบราว์เซอร์จึงปลอดภัยกว่าที่จะระบุ
Lawrence Wagerfield

7
ดูเหมือนว่าการส่งออกเก็บจะสร้างฝันร้ายของการนำเข้าแบบวนรอบ createStore ประกอบด้วยตัวลดขนาดของคุณซึ่งต้องการการกระทำของคุณ (อย่างน้อยประเภทการกระทำ enum และอินเทอร์เฟซการกระทำ) ซึ่งจะต้องไม่นำเข้าสิ่งใด ๆ ดังนั้นคุณต้องไม่ใช้ร้านค้าในอุปกรณ์ลดหรือการกระทำของคุณ (หรือโต้เถียงวิธีอื่น ๆ เกี่ยวกับการนำเข้าแบบวนรอบ)
Eloff

6
นี่คือคำตอบที่ถูกต้อง แต่ถ้าคุณ (เช่นฉัน) ต้องการอ่านแทนที่จะส่งการกระทำในร้านคุณต้องโทรหาstore.getState()
Juan JoséRamírez

3
การตีความของฉันใน "access redux store" คือ "read" store แค่พยายามช่วยเหลือผู้อื่น
Juan JoséRamírez

47

พบทางออก ดังนั้นฉันจึงนำเข้าร้านค้าใน api util ของฉันและสมัครสมาชิกที่นั่น และในฟังก์ชั่นฟังฉันตั้งค่าเริ่มต้นทั่วโลกของ axios ด้วยโทเค็นที่ดึงมาใหม่ของฉัน

นี่คือapi.jsลักษณะใหม่ของฉัน:

// tooling modules
import axios from 'axios'

// store
import store from '../store'
store.subscribe(listener)

function select(state) {
  return state.auth.tokens.authentication_token
}

function listener() {
  let token = select(store.getState())
  axios.defaults.headers.common['Authorization'] = token;
}

// configuration
const api = axios.create({
  baseURL: 'http://localhost:5001/api/v1',
  headers: {
    'Content-Type': 'application/json',
  }
})

export default api

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


1
คุณสามารถแชร์store.jsไฟล์ของคุณเป็นอย่างไร
Vic

สิ่งที่ฉันกำลังมองหาขอขอบคุณ ton @subodh
Harkirat Saluja

1
ฉันรู้ว่าคำถามนั้นเก่า แต่คุณสามารถยอมรับคำตอบของคุณเองว่าถูกต้อง ทำให้คำตอบของคุณง่ายขึ้นที่จะหาคนอื่นที่อาจมาที่นี่ในที่สุด
Filipe Merker

3
ฉันได้รับ "TypeError: WEBPACK_IMPORTED_MODULE_2__store_js .b ไม่ได้กำหนด" เมื่อฉันพยายามเข้าถึงร้านค้านอกองค์ประกอบหรือฟังก์ชั่น ความช่วยเหลือเกี่ยวกับสาเหตุที่เป็นอะไร
Ben

21

คุณสามารถใช้storeวัตถุที่ส่งคืนจากcreateStoreฟังก์ชัน (ซึ่งควรใช้ในรหัสของคุณในการเริ่มต้นแอปแล้ว) คุณสามารถใช้วัตถุนี้เพื่อรับสถานะปัจจุบันด้วยstore.getState()วิธีการหรือstore.subscribe(listener)สมัครรับข้อมูลเพื่ออัปเดตร้านค้า

คุณสามารถบันทึกวัตถุนี้ไว้ในwindowคุณสมบัติเพื่อเข้าถึงได้จากส่วนใด ๆ ของแอปพลิเคชันหากคุณต้องการ (window.store = store )

ข้อมูลเพิ่มเติมสามารถพบได้ในเอกสาร Redux


19
ฟังดูค่อนข้างแฮ็กเพื่อบันทึกstoreไปที่window
วิ

3
@Vic แน่นอนว่ามันเป็น :) และโดยปกติคุณไม่ต้องการทำมัน ฉันแค่อยากจะบอกว่าคุณสามารถทำสิ่งที่คุณต้องการด้วยตัวแปรนี้ อาจเป็นความคิดที่ดีที่สุดที่จะเก็บไว้ในไฟล์ที่คุณสร้างชีวิต "createStore" ของคุณแล้วนำเข้าจากมัน
trashgenerator

ฉันมี iframe หลายรายการที่ต้องการเข้าถึงสถานะของ iframe อื่น ๆ ฉันรู้ว่ามันแฮ็กค่อนข้าง แต่ฉันคิดว่าการมีร้านค้าบนหน้าต่างจะดีกว่าการใช้ข้อความไปยัง iframes ความคิดใด ๆ ?? @Vic @trashgenerator?
Sean Malter


10

เช่นเดียวกับ @sanchit มิดเดิลแวร์ที่เสนอเป็นทางออกที่ดีถ้าคุณได้กำหนดอินสแตนซ์ axios ของคุณทั่วโลกแล้ว

คุณสามารถสร้างมิดเดิลแวร์เช่น:

function createAxiosAuthMiddleware() {
  return ({ getState }) => next => (action) => {
    const { token } = getState().authentication;
    global.axios.defaults.headers.common.Authorization = token ? `Bearer ${token}` : null;

    return next(action);
  };
}

const axiosAuth = createAxiosAuthMiddleware();

export default axiosAuth;

และใช้มันเช่นนี้

import { createStore, applyMiddleware } from 'redux';
const store = createStore(reducer, applyMiddleware(axiosAuth))

มันจะตั้งค่าโทเค็นในทุกการกระทำ แต่คุณสามารถฟังการกระทำที่เปลี่ยนโทเค็นเท่านั้น


คุณจะประสบความสำเร็จเช่นเดียวกันกับอินสแตนซ์ axios ที่กำหนดเองได้อย่างไร
Anatol

3

สำหรับ TypeScript 2.0 มันจะเป็นดังนี้:

MyStore.ts

export namespace Store {

    export type Login = { isLoggedIn: boolean }

    export type All = {
        login: Login
    }
}

import { reducers } from '../Reducers'
import * as Redux from 'redux'

const reduxStore: Redux.Store<Store.All> = Redux.createStore(reducers)

export default reduxStore;

MyClient.tsx

import reduxStore from "../Store";
{reduxStore.dispatch(...)}

3
หากคุณลงคะแนนโปรดเพิ่มความคิดเห็นว่าทำไม
Ogglas

3
โหวตไม่ลงเพราะคำตอบของคุณไม่มีคำอธิบายและใช้ TypeScript แทน Javascript
จะ

2
@ Will ขอบคุณที่พูดว่าทำไม Imao รหัสไม่จำเป็นต้องมีสเปค แต่ถ้าคุณต้องการสิ่งที่เฉพาะเจาะจงอธิบายโปรดพูดในสิ่งที่ TypeScript ถูกนำมาใช้อย่างแน่นอน แต่หากการลบการพิมพ์รหัสเดียวกันจะทำงานใน ES6 โดยไม่มีปัญหา
Ogglas

โปรดทราบว่าสิ่งนี้จะไม่ดีมากหากคุณกำลังทำการเรนเดอร์ด้านข้างมันจะแชร์สถานะกับคำขอทั้งหมด
Rob Evans

2

อาจจะช้าไปหน่อย แต่ฉันคิดว่าวิธีที่ดีที่สุดคือใช้ axios.interceptorsดังต่อไปนี้ URL ที่นำเข้าอาจเปลี่ยนแปลงไปตามการตั้งค่าโครงการของคุณ

index.js

import axios from 'axios';
import setupAxios from './redux/setupAxios';
import store from './redux/store';

// some other codes

setupAxios(axios, store);

setupAxios.js

export default function setupAxios(axios, store) {
    axios.interceptors.request.use(
        (config) => {
            const {
                auth: { tokens: { authorization_token } },
            } = store.getState();

            if (authorization_token) {
                config.headers.Authorization = `Bearer ${authorization_token}`;
            }

            return config;
        },
       (err) => Promise.reject(err)
    );
}

1

ทำมันด้วยตะขอ ฉันพบปัญหาที่คล้ายกัน แต่ฉันใช้ react-redux กับ hooks ฉันไม่ต้องการที่จะเพิ่มโค้ดอินเทอร์เฟซของฉัน (เช่นการตอบสนองต่อส่วนประกอบ) โดยมีรหัสจำนวนมากที่ใช้ในการดึง / ส่งข้อมูลจาก / ไปยังร้านค้า แต่ฉันต้องการฟังก์ชั่นที่มีชื่อสามัญเพื่อดึงและอัปเดตข้อมูล เส้นทางของฉันคือการใส่แอพ

const store = createSore(
   allReducers,
   window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
 );

ลงในโมดูลชื่อstore.jsและการเพิ่มexportก่อนconstและเพิ่มการนำเข้า react-redux ตามปกติใน store.js ไฟล์. จากนั้นฉันก็นำเข้าสู่index.jsระดับแอปซึ่งฉันก็นำเข้าสู่ index.js ด้วยimport {store} from "./store.js"องค์ประกอบทั่วไปจากนั้นลูก ๆ เข้าถึงร้านค้าโดยใช้useSelector()และuseDispatch() hooks

ในการเข้าถึงร้านค้าในโค้ดส่วนหน้าไม่ใช่ส่วนประกอบฉันใช้การนำเข้าแบบอะนาล็อก (เช่นimport {store} from "../../store.js") จากนั้นใช้store.getState()และstore.dispatch({*action goes here*})เพื่อจัดการการดึงและอัปเดต (เอ่อส่งการกระทำไปที่) ร้านค้า


0

วิธีที่ง่ายในการเข้าถึงโทเค็นคือการใส่โทเค็นใน LocalStorage หรือ AsyncStorage พร้อม React Native

ด้านล่างตัวอย่างด้วยโปรเจ็กต์React Native

authReducer.js

import { AsyncStorage } from 'react-native';
...
const auth = (state = initialState, action) => {
  switch (action.type) {
    case SUCCESS_LOGIN:
      AsyncStorage.setItem('token', action.payload.token);
      return {
        ...state,
        ...action.payload,
      };
    case REQUEST_LOGOUT:
      AsyncStorage.removeItem('token');
      return {};
    default:
      return state;
  }
};
...

และ api.js

import axios from 'axios';
import { AsyncStorage } from 'react-native';

const defaultHeaders = {
  'Content-Type': 'application/json',
};

const config = {
  ...
};

const request = axios.create(config);

const protectedRequest = options => {
  return AsyncStorage.getItem('token').then(token => {
    if (token) {
      return request({
        headers: {
          ...defaultHeaders,
          Authorization: `Bearer ${token}`,
        },
        ...options,
      });
    }
    return new Error('NO_TOKEN_SET');
  });
};

export { request, protectedRequest };

สำหรับเว็บคุณสามารถใช้Window.localStorageแทน AsyncStorage

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