使用 React 上下文维护用户状态

Jes*_*ess 6 reactjs react-router

我正在尝试使用 React 的上下文功能在整个应用程序中维护有关用户的信息(例如,用户 ID,它将在各种页面的 API 调用中使用)。我知道这是一个未记录且不推荐使用 Redux 的应用程序,但我的应用程序非常简单(所以我不想要或不需要 Redux 的复杂性),这似乎是上下文的常见且合理的用例。不过,如果有更多可接受的解决方案来在整个应用程序中全局保存用户信息,我愿意使用更好的方法。

但是,我对如何正确使用它感到困惑:一旦用户通过 AuthPage(ContextProvider 的子项)登录,我如何更新 ContextProvider 中的上下文,以便它可以访问其他组件,例如 FridgePage?(是的,从技术上讲,上下文不应该更新,但这是一次性操作——如果有人知道在 ContextProvider 初始化时执行此操作的方法,那会更理想)。路由器会碍事吗?

我在这里复制了相关组件。

索引.js

import React from 'react'; 
import ReactDOM from 'react-dom';
import { HashRouter, Route, Switch } from 'react-router-dom';

import Layout from './components/Layout.jsx';
import AuthPage from './components/AuthPage.jsx';
import ContextProvider from './components/ContextProvider.jsx';

ReactDOM.render(
    <ContextProvider>
        <HashRouter>
            <Switch>
                <Route path="/login" component={AuthPage} />
                <Route path="/" component={Layout} />
            </Switch>
        </HashRouter>
    </ContextProvider>,
    document.getElementById('root')
);
Run Code Online (Sandbox Code Playgroud)

ContextProvider.jsx

import React from 'react';
import PropTypes from 'prop-types';

export default class ContextProvider extends React.Component {
    static childContextTypes = {
        user: PropTypes.object
    }

    // called every time the state changes
    getChildContext() {
        return { user: this.state.user };
    }

    render() {
        return(
            <div>
                { this.props.children }
            </div>
        );
    }
}
Run Code Online (Sandbox Code Playgroud)

AuthPage.jsx

import React from 'react';
import PropTypes from 'prop-types';

import AuthForm from './AuthForm.jsx';
import RegisterForm from './RegisterForm.jsx';
import Api from '../api.js';

export default class AuthPage extends React.Component { 
    static contextTypes = {
        user: PropTypes.object
    }

    constructor(props) {
        super(props);
        this.updateUserContext = this.updateUserContext.bind(this);
    }

    updateUserContext(user) {
        console.log("Updating user context");
        this.context.user = user;
        console.log(this.context.user);
    }

    render() {
        return (
            <div>
                <AuthForm type="Login" onSubmit={Api.login} updateUser={this.updateUserContext} />
                <AuthForm type="Register" onSubmit={Api.register} updateUser={this.updateUserContext} />
            </div>
        );
    }
}
Run Code Online (Sandbox Code Playgroud)

布局.jsx

import React from 'react';
import Header from './Header.jsx';
import { Route, Switch } from 'react-router-dom';

import FridgePage from './FridgePage.jsx';
import StockPage from './StockPage.jsx';

export default class Layout extends React.Component {
    render() {
        return (
            <div>
                <Header />
                <Switch>
                    <Route exact path="/stock" component={StockPage} />
                    <Route exact path="/" component={FridgePage} />
                </Switch>
            </div>
        );
    }
}
Run Code Online (Sandbox Code Playgroud)

FridgePage.jsx(我想访问的地方this.context.user

import React from 'react';
import PropTypes from 'prop-types';

import Api from '../api.js';

export default class FridgePage extends React.Component {
    static contextTypes = {
        user: PropTypes.object
    }

    constructor(props) {
        super(props);

        this.state = {
            fridge: []
        }
    }

    componentDidMount() {
        debugger;
        Api.getFridge(this.context.user.id)
            .then((fridge) => {
                this.setState({ "fridge": fridge });
            })
            .catch((err) => console.log(err));
    }

    render() {
        return (
            <div>
                <h1>Fridge</h1>
                { this.state.fridge }
            </div>
        );
    }
}
Run Code Online (Sandbox Code Playgroud)

mar*_*lin 3

简单状态提供者

auth模块提供两个功能:

withAuth- 高阶组件向需要的组件提供身份验证数据。

update- 更新认证状态的功能

怎么运行的

基本思想是withAuth应该将身份验证数据添加到传递给包装组件的道具中。它分三个步骤完成:获取传递给组件的 props、添加身份验证数据、将新 props 传递给组件。

let state = "initial state"

const withAuth = (Component) => (props) => {
  const newProps = {...props, auth: state }
  return <Component {...newProps} />
}
Run Code Online (Sandbox Code Playgroud)

缺少的一件事是当身份验证状态发生变化时重新渲染组件。有两种重新渲染组件的方法:withsetState()forceUpdate()。由于withAuth不需要内部状态,我们将用于forceUpdate()重新渲染。

每当身份验证状态发生变化时,我们都需要触发组件重新渲染。为此,我们需要将forceUpdate()函数存储在一个函数可以访问的位置,update()只要身份验证状态发生变化,函数就会调用它。

let state = "initial state"

// this stores forceUpdate() functions for all mounted components
// that need auth state
const rerenderFunctions = []

const withAuth = (Component) =>
    class WithAuth extends React.Component {
    componentDidMount() {
        const rerenderComponent = this.forceUpdate.bind(this)
        rerenderFunctions.push(rerenderComponent)
    }
    render() {
      const newProps = {...props, auth: state }
      return <Component {...newProps} />
    }
  }

const update = (newState) => {
    state = newState
  // rerender all wrapped components to reflect current auth state
  rerenderFunctions.forEach((rerenderFunction) => rerenderFunction())
}
Run Code Online (Sandbox Code Playgroud)

最后一步是添加代码,以便在卸载组件时删除重新渲染功能

let state = "initial state"

const rerenderFunctions = []

const unsubscribe = (rerenderFunciton) => {
  // find position of rerenderFunction
  const index = subscribers.findIndex(subscriber);
  // remove it
  subscribers.splice(index, 1);
}

const subscribe = (rerenderFunction) => {
  // for convinience, subscribe returns a function to
  // remove the rerendering when it is no longer needed
  rerenderFunctions.push(rerenderFunction)
  return () => unsubscribe(rerenderFunction)
}

const withAuth = (Component) =>
    class WithAuth extends React.Component {
    componentDidMount() {
        const rerenderComponent = this.forceUpdate.bind(this)

        this.unsubscribe = subscribe(rerenderComponent)
    }
    render() {
      const newProps = {...props, auth: state }
      return <Component {...newProps} />
    }
    componentWillUnmount() {
        // remove rerenderComponent function
        // since this component don't need to be rerendered
        // any more
        this.unsubscribe()
    }
  }
Run Code Online (Sandbox Code Playgroud)

let state = "initial state"

const withAuth = (Component) => (props) => {
  const newProps = {...props, auth: state }
  return <Component {...newProps} />
}
Run Code Online (Sandbox Code Playgroud)
let state = "initial state"

// this stores forceUpdate() functions for all mounted components
// that need auth state
const rerenderFunctions = []

const withAuth = (Component) =>
    class WithAuth extends React.Component {
    componentDidMount() {
        const rerenderComponent = this.forceUpdate.bind(this)
        rerenderFunctions.push(rerenderComponent)
    }
    render() {
      const newProps = {...props, auth: state }
      return <Component {...newProps} />
    }
  }

const update = (newState) => {
    state = newState
  // rerender all wrapped components to reflect current auth state
  rerenderFunctions.forEach((rerenderFunction) => rerenderFunction())
}
Run Code Online (Sandbox Code Playgroud)

游乐场: https: //codesandbox.io/s/vKwyxYO0