113

我正在使用新的 React Context API 而不是 Redux 开发一个新应用程序,之前,使用Redux,例如,当我需要获取用户列表时,我只需调用componentDidMount我的操作,但现在使用 React Context,我的操作就在里面我的消费者在我的渲染函数中,这意味着每次调用我的渲染函数时,它都会调用我的操作来获取我的用户列表,这不好,因为我会做很多不必要的请求。

那么,我如何才能只调用一次我的操作,比如 incomponentDidMount而不是调用 render?

举个例子,看看这段代码:

假设我将所有组件包装Providers在一个组件中,如下所示:

import React from 'react';

import UserProvider from './UserProvider';
import PostProvider from './PostProvider';

export default class Provider extends React.Component {
  render(){
    return(
      <UserProvider>
        <PostProvider>
          {this.props.children}
        </PostProvider>
      </UserProvider>
    )
  }
}

然后我把这个 Provider 组件包装了我的所有应用程序,如下所示:

import React from 'react';
import Provider from './providers/Provider';
import { Router } from './Router';

export default class App extends React.Component {
  render() {
    const Component = Router();
    return(
      <Provider>
        <Component />
      </Provider>
    )
  }
}

现在,例如,在我的用户视图中,它将是这样的:

import React from 'react';
import UserContext from '../contexts/UserContext';

export default class Users extends React.Component {
  render(){
    return(
      <UserContext.Consumer>
        {({getUsers, users}) => {
          getUsers();
          return(
            <h1>Users</h1>
            <ul>
              {users.map(user) => (
                <li>{user.name}</li>
              )}
            </ul>
          )
        }}
      </UserContext.Consumer>
    )
  }
}

我想要的是这样的:

import React from 'react';
import UserContext from '../contexts/UserContext';

export default class Users extends React.Component {
  componentDidMount(){
    this.props.getUsers();
  }

  render(){
    return(
      <UserContext.Consumer>
        {({users}) => {
          getUsers();
          return(
            <h1>Users</h1>
            <ul>
              {users.map(user) => (
                <li>{user.name}</li>
              )}
            </ul>
          )
        }}
      </UserContext.Consumer>
    )
  }
}

但是,上面的示例当然不起作用,因为getUsers它不在我的用户视图道具中。如果可能的话,正确的方法是什么?

4

5 回答 5

168

编辑:随着v16.8.0中 react-hooks 的引入,您可以通过使用useContext钩子在功能组件中使用上下文

const Users = () => {
    const contextValue = useContext(UserContext);
    // rest logic here
}

编辑:从版本16.6.0开始。您可以使用this.contextlike在生命周期方法中使用上下文

class Users extends React.Component {
  componentDidMount() {
    let value = this.context;
    /* perform a side-effect at mount using the value of UserContext */
  }
  componentDidUpdate() {
    let value = this.context;
    /* ... */
  }
  componentWillUnmount() {
    let value = this.context;
    /* ... */
  }
  render() {
    let value = this.context;
    /* render something based on the value of UserContext */
  }
}
Users.contextType = UserContext; // This part is important to access context values

在 16.6.0 版本之前,您可以通过以下方式进行操作

为了在你的生命周期方法中使用上下文,你可以编写你的组件,比如

class Users extends React.Component {
  componentDidMount(){
    this.props.getUsers();
  }

  render(){
    const { users } = this.props;
    return(

            <h1>Users</h1>
            <ul>
              {users.map(user) => (
                <li>{user.name}</li>
              )}
            </ul>
    )
  }
}
export default props => ( <UserContext.Consumer>
        {({users, getUsers}) => {
           return <Users {...props} users={users} getUsers={getUsers} />
        }}
      </UserContext.Consumer>
)

通常,您会在您的应用程序中维护一个上下文,并且将上述登录信息打包到 HOC 中以便重用它是有意义的。你可以这样写

import UserContext from 'path/to/UserContext';

const withUserContext = Component => {
  return props => {
    return (
      <UserContext.Consumer>
        {({users, getUsers}) => {
          return <Component {...props} users={users} getUsers={getUsers} />;
        }}
      </UserContext.Consumer>
    );
  };
};

然后你可以像使用它一样

export default withUserContext(User);
于 2018-04-13T06:47:03.527 回答
3

好的,我找到了一种有限制的方法。通过该with-context库,我设法将所有消费者数据插入到我的组件道具中。

但是,要将多个消费者插入到同一个组件中是很复杂的事情,您必须使用这个库创建混合消费者,这使得代码不优雅并且没有生产力。

该库的链接:https ://github.com/SunHuawei/with-context

编辑:实际上您不需要使用with-context提供的多上下文 api,实际上,您可以使用简单的 api 并为每个上下文制作一个装饰器,如果您想在组件中使用多个使用者,只需在您的班级上方声明您想要的尽可能多的装饰器!

于 2018-04-13T06:44:49.113 回答
0

The following is working for me. This is a HOC that uses useContext and useReducer hooks. There's also a way to interact with sockets in this example.

I'm creating 2 contexts (one for dispatch and one for state). You would first need to wrap some outer component with the SampleProvider HOC. Then by using one or more of the utility functions, you can gain access to the state and/or the dispatch. The withSampleContext is nice because it passes both the dispatch and state. There are also other functions like useSampleState and useSampleDispatch that can be used within a functional component.

This approach allows you to code your React components as you always have without needing to inject any Context specific syntax.

import React, { useEffect, useReducer } from 'react';
import { Client } from '@stomp/stompjs';
import * as SockJS from 'sockjs-client';

const initialState = {
  myList: [],
  myObject: {}
};

export const SampleStateContext = React.createContext(initialState);
export const SampleDispatchContext = React.createContext(null);

const ACTION_TYPE = {
  SET_MY_LIST: 'SET_MY_LIST',
  SET_MY_OBJECT: 'SET_MY_OBJECT'
};

const sampleReducer = (state, action) => {
  switch (action.type) {
    case ACTION_TYPE.SET_MY_LIST:
      return {
        ...state,
        myList: action.myList
      };
    case ACTION_TYPE.SET_MY_OBJECT:
      return {
        ...state,
        myObject: action.myObject
      };
    default: {
      throw new Error(`Unhandled action type: ${action.type}`);
    }
  }
};

/**
 * Provider wrapper that also initializes reducer and socket communication
 * @param children
 * @constructor
 */
export const SampleProvider = ({ children }: any) => {
  const [state, dispatch] = useReducer(sampleReducer, initialState);
  useEffect(() => initializeSocket(dispatch), [initializeSocket]);

  return (
    <SampleStateContext.Provider value={state}>
      <SampleDispatchContext.Provider value={dispatch}>{children}</SampleDispatchContext.Provider>
    </SampleStateContext.Provider>
  );
};

/**
 * HOC function used to wrap component with both state and dispatch contexts
 * @param Component
 */
export const withSampleContext = Component => {
  return props => {
    return (
      <SampleDispatchContext.Consumer>
        {dispatch => (
          <SampleStateContext.Consumer>
            {contexts => <Component {...props} {...contexts} dispatch={dispatch} />}
          </SampleStateContext.Consumer>
        )}
      </SampleDispatchContext.Consumer>
    );
  };
};

/**
 * Use this within a react functional component if you want state
 */
export const useSampleState = () => {
  const context = React.useContext(SampleStateContext);
  if (context === undefined) {
    throw new Error('useSampleState must be used within a SampleProvider');
  }
  return context;
};

/**
 * Use this within a react functional component if you want the dispatch
 */
export const useSampleDispatch = () => {
  const context = React.useContext(SampleDispatchContext);
  if (context === undefined) {
    throw new Error('useSampleDispatch must be used within a SampleProvider');
  }
  return context;
};

/**
 * Sample function that can be imported to set state via dispatch
 * @param dispatch
 * @param obj
 */
export const setMyObject = async (dispatch, obj) => {
  dispatch({ type: ACTION_TYPE.SET_MY_OBJECT, myObject: obj });
};

/**
 * Initialize socket and any subscribers
 * @param dispatch
 */
const initializeSocket = dispatch => {
  const client = new Client({
    brokerURL: 'ws://path-to-socket:port',
    debug: function (str) {
      console.log(str);
    },
    reconnectDelay: 5000,
    heartbeatIncoming: 4000,
    heartbeatOutgoing: 4000
  });

  // Fallback code for http(s)
  if (typeof WebSocket !== 'function') {
    client.webSocketFactory = function () {
      return new SockJS('https://path-to-socket:port');
    };
  }

  const onMessage = msg => {
    dispatch({ type: ACTION_TYPE.SET_MY_LIST, myList: JSON.parse(msg.body) });
  };

  client.onConnect = function (frame) {
    client.subscribe('/topic/someTopic', onMessage);
  };

  client.onStompError = function (frame) {
    console.log('Broker reported error: ' + frame.headers['message']);
    console.log('Additional details: ' + frame.body);
  };
  client.activate();
};
于 2020-12-18T16:36:54.527 回答
0

.bind(this)就我而言,添加到活动中就足够了。这就是我的组件的样子。

// Stores File
class RootStore {
   //...States, etc
}
const myRootContext = React.createContext(new RootStore())
export default myRootContext;


// In Component
class MyComp extends Component {
  static contextType = myRootContext;

  doSomething() {
   console.log()
  }

  render() {
    return <button onClick={this.doSomething.bind(this)}></button>
  }
}
于 2019-07-15T14:36:28.987 回答
-14

您必须在更高的父组件中传递上下文才能在子组件中作为道具进行访问。

于 2018-06-19T10:17:10.767 回答