React 路由器更改 url 但未更新上下文

Posted

技术标签:

【中文标题】React 路由器更改 url 但未更新上下文【英文标题】:React router changes url but not context is not updated 【发布时间】:2020-04-17 16:29:08 【问题描述】:

我有一个页面模板,其中包含一组不同的键,用于以用户选择的语言显示文本。默认语言为英文,英文页面对应的URL为localhost:3000。用户可以选择通过单击按钮切换到西班牙语。当用户单击按钮以切换到西班牙语时,将调用 history.push() 函数。它使用给定的国家代码更新 URL,在这种情况下 URL 变为 localhost:3000/es。单击浏览器后退按钮时,URL 会变回 localhost:3000,但视图仍为西班牙语。

我在这个问题上苦苦挣扎了一段时间,并尝试实施多种解决方案,其中包括:

使用WithRouter 组件包装组件; 使用Router 组件将历史对象作为道具传递; 使用componentDidUpdate强制更新组件; App 组件中的调用调度;

我的index.js 文件包含渲染AppWrapper 组件的路由。 AppWrapper 组件呈现 App 组件,它是 I18nContextProvider 组件的子组件。

//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import  BrowserRouter, Route, Switch  from 'react-router-dom';

import App from './App';
import  I18nContextProvider, availableTranslationsEnum  from './languages/languageHandler';

const AppWrapper = (language) => (
  <I18nContextProvider language=language>
    <App />
  </I18nContextProvider>
);

const appRoutes = (
  <BrowserRouter>
    <Switch>
      <Route
        path=`/$availableTranslationsEnum.spanish`
        render=() => AppWrapper(availableTranslationsEnum.spanish)
        exact
      />
      <Route
        render=() => AppWrapper(availableTranslationsEnum.english)
      />
    </Switch>
  </BrowserRouter>
);

ReactDOM.render(appRoutes, document.getElementById('root'));

I18nContextProvider 组件在 languageHandler.js 文件中定义,如下所示。

//languageHandler.js
import React,  useReducer  from 'react';
import EN from './en.json';
import ES from './es.json';

const translations = 
  en: EN,
  es: ES,
;

const getTranslate = (langCode) => (key) => translations[langCode][key] || key;

export const availableTranslationsEnum = 
  english: Object.keys(translations)[0],
  spanish: Object.keys(translations)[1],
;

const setInitialState = (language) => 
  const defaultLanguage = availableTranslationsEnum.english;
  const siteLanguage = language || defaultLanguage;
  return 
    langCode: siteLanguage,
    translate: getTranslate(siteLanguage),
  ;
;

export const I18nContext = React.createContext(setInitialState());

export const I18nContextProvider = ( children, language ) => 
  const reducer = (action) => 
    switch (action.type) 
      case 'setLanguage':
        return 
          langCode: action.payload,
          translate: getTranslate(action.payload),
        ;
      default:
        return  ...setInitialState(language) ;
    
  ;

  const [state, dispatch] = useReducer(reducer, setInitialState(language));

  return (
    <I18nContext.Provider value= ...state, dispatch >
      children
    </I18nContext.Provider>
  );
;

App 组件包含翻译键,如下所示。

//App.js
import React from 'react';
import logo from './logo.svg';
import './App.css';

import  I18nContext  from './languages/languageHandler';
import LanguageSelect from './components/LanguageSelect';

const App = () => 
  const  translate  = React.useContext(I18nContext);

  return (
    <div className="App">
      <header className="App-header">
        <img src=logo className="App-logo"  />
        <LanguageSelect />
        <p>
          translate('edit_and_save')
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          translate('learn_react')
        </a>
      </header>
    </div>
  );
;

export default App;

App 组件渲染LanguageSelect 组件,看起来像这样。

//LanguageSelect.js
import React,  useContext  from 'react';
import  useHistory  from 'react-router-dom';
import  I18nContext, availableTranslationsEnum  from '../languages/languageHandler';

const LanguageSelect = () => 
  const history = useHistory();
  const  langCode, dispatch  = useContext(I18nContext);

  const switchLanguage = (languageToSwitchTo) => 
    const dispatchType = 'setLanguage';
    dispatch( type: dispatchType, payload: languageToSwitchTo );
    if (languageToSwitchTo !== availableTranslationsEnum.english) 
      history.push(`/$languageToSwitchTo`);
    

    if (languageToSwitchTo === availableTranslationsEnum.english) 
      history.push(`/`); //Default language
    

  ;

  if (langCode === availableTranslationsEnum.english) 
    return (
      <button onClick=() => switchLanguage(availableTranslationsEnum.spanish)>Español</button>
    );
  

  return (<button onClick=() => switchLanguage(availableTranslationsEnum.english)>English</button>);
;

export default LanguageSelect;

我试图了解为什么 URL 发生了变化,但组件保持原样。如果有人可以帮助我将我推向正确的方向,我们将不胜感激。

【问题讨论】:

【参考方案1】:

使用 react 开发工具检查组件的 props 和 states - 无需更改,无需重新渲染。

您的减速器适用于“事件”...更改 url(后退按钮)会导致 prop 更改,而不是动作调度。

&lt;I18nContext.Provider&gt; 使用状态,而不是道具 - 在道具更改时保持不变,没有理由重新渲染。

只需检查道具并更新渲染函数以了解道具更改。

【讨论】:

【参考方案2】:

将您的语言值添加到 basename 属性到 &lt;BrowserRouter&gt; 组件,它将为您的所有路由创建一个基本路径。

const appRoutes = props => (
  <BrowserRouter basename=props.language>
    <I18nContextProvider language=props.language>
      <App />
    </I18nContextProvider>
  </BrowserRouter>
);

如果语言是英语,那么您的网址将是:

localhost:3000/en

对于您拥有的其他语言并且基本名称不会改变。由用户使用 push 功能。

【讨论】:

你能再解释一下吗?一旦用户单击浏览器的后退按钮,如何通过语言的值以及添加基本名称来更新组件? 无需检查返回按钮,只需将应用组件连接到商店以获取所选语言并将其作为基本名称添加到 BrowserRoute 组件。 将我的代码更改为&lt;BrowserRouter basename=props.language&gt;&lt;I18nContextProvider language =props.language&gt;&lt;App /&gt;&lt;/I18nContextProvider&gt; &lt;/BrowserRouter&gt; 时,会引发错误TypeError: Cannot read property edit_and_save of undefinedlanguageHandler.js 中引用getTranslate()

以上是关于React 路由器更改 url 但未更新上下文的主要内容,如果未能解决你的问题,请参考以下文章

React 路由器推送仅更改 url

React 不会在路由参数更改或查询更改时重新加载组件数据

React Router 备忘清单_开发速查表分享

React,redux 组件不会在路由更改时更新

React 路由器 - 更新 URL 哈希而不重新渲染页面

每次 URL 更改时,带有 browserHistory 的 React 路由器都会转到服务器