React 上下文 api - 消费者在上下文更改后不重新呈现

React Context api - Consumer Does Not re-render after context changed

我搜索了答案但找不到任何答案,所以我在这里问, 我有一个更新上下文的消费者, 和另一个应该显示上下文的消费者。 我正在使用 react with typescript(16.3)

上下文(AppContext.tsx):

export interface AppContext {
    jsonTransactions: WithdrawTransactionsElement | null;
    setJsonTran(jsonTransactions: WithdrawTransactionsElement | null): void;
}

export const appContextInitialState : AppContext = {
    jsonTransactions: null,
    setJsonTran : (data: WithdrawTransactionsElement) => {
        return appContextInitialState.jsonTransactions = data;
    }
};

export const AppContext = React.createContext(appContextInitialState);

制作人(App.tsx):

interface Props {}

class App extends React.Component<Props, AppContext> {

  state: AppContext = appContextInitialState;

  constructor(props : Props) {
    super(props);
  }

  render() {
    return (
        <AppContext.Provider value={this.state}>
          <div className="App">
            <header className="App-header">
              <SubmitTransactionFile/>
              <WithdrawTransactionsTable />
            </header>
          </div>
        </AppContext.Provider>
    );
  }
}

export default App;

更新上下文消费者(SubmitTransactionFile.tsx)

class SubmitTransactionFile extends React.Component {

    private fileLoadedEvent(file: React.ChangeEvent<HTMLInputElement>, context: AppContext): void{
        let files = file.target.files;
        let reader = new FileReader();
        if (files && files[0]) {
            reader.readAsText(files[0]);
            reader.onload = (json) =>  {
                if (json && json.target) {
                    // @ts-ignore -> this is because result field is not recognized by typescript compiler
                    context.setJsonTran(JSON.parse(json.target.result))
                }
            }
        }
    }

    render() {
        return (
            <AppContext.Consumer>
                { context  =>
                    <div className="SubmitTransactionFile">
                        <label>Select Transaction File</label><br />
                        <input type="file" id="file" onChange={(file) =>
                            this.fileLoadedEvent(file, context)} />
                        <p>{context.jsonTransactions}</p>
                    </div>
                }
            </AppContext.Consumer>
        )
    }
}


export default SubmitTransactionFile;

最后显示消费者(WithdrawTransactionsTable.tsx):

class WithdrawTransactionsTable extends React.Component {

    render() {
        return (
            <AppContext.Consumer>
                { context  =>
                    <div>
                        <label>{context.jsonTransactions}</label>
                    </div>
                }
            </AppContext.Consumer>
        )
    }
}

export default WithdrawTransactionsTable;

据我了解,在调用 fileLoadedEvent 函数后,context.setJsonTran 应该重新渲染其他消费者,并且 WithdrawTransactionsTable 组件应该重新渲染,但事实并非如此。

我做错了什么?

更新状态时,不会触发提供者的重新呈现,因此消费者数据不会更改。您应该使用 setState 更新状态并将上下文值分配给提供者,如

class App extends React.Component<Props, AppContext> {
  constructor(props : Props) {
    super(props);
    this.state = {
         jsonTransactions: null,
         setJsonTran: this.setJsonTran
    };
  }

  setJsonTran : (data: WithdrawTransactionsElement) => {
        this.setState({
             jsonTransactions: data
        });
  }

  render() {
    return (
        <AppContext.Provider value={this.state}>
          <div className="App">
            <header className="App-header">
              <SubmitTransactionFile/>
              <WithdrawTransactionsTable />
            </header>
          </div>
        </AppContext.Provider>
    );
  }
}

export default App;

你的 setJsonTran 只是改变了上下文的默认值,这不会导致 Providervalue 改变。

您可以改为将 jsonTransactions 保持在最顶层状态,并传递一个函数来更改此状态,进而更新 value

例子

const AppContext = React.createContext();

class App extends React.Component {
  state = {
    jsonTransactions: null
  };

  setJsonTran = data => {
    this.setState({ jsonTransactions: data });
  };

  render() {
    const context = this.state;
    context.setJsonTran = this.setJsonTran;

    return (
      <AppContext.Provider value={context}>
        <div className="App">
          <header className="App-header">
            <SubmitTransactionFile />
            <WithdrawTransactionsTable />
          </header>
        </div>
      </AppContext.Provider>
    );
  }
}

const AppContext = React.createContext();

class App extends React.Component {
  state = {
    jsonTransactions: null
  };

  setJsonTran = data => {
    this.setState({ jsonTransactions: data });
  };

  render() {
    const context = this.state;
    context.setJsonTran = this.setJsonTran;

    return (
      <AppContext.Provider value={context}>
        <div className="App">
          <header className="App-header">
            <SubmitTransactionFile />
            <WithdrawTransactionsTable />
          </header>
        </div>
      </AppContext.Provider>
    );
  }
}

class SubmitTransactionFile extends React.Component {
  fileLoadedEvent(file, context) {
    let files = file.target.files;
    let reader = new FileReader();
    if (files && files[0]) {
      reader.readAsText(files[0]);
      reader.onload = json => {
        if (json && json.target) {
          // slice just to not output too much in this example
          context.setJsonTran(json.target.result.slice(0, 10));
        }
      };
    }
  }

  render() {
    return (
      <AppContext.Consumer>
        {context => (
          <div className="SubmitTransactionFile">
            <label>Select Transaction File</label>
            <br />
            <input
              type="file"
              id="file"
              onChange={file => this.fileLoadedEvent(file, context)}
            />
            <p>{context.jsonTransactions}</p>
          </div>
        )}
      </AppContext.Consumer>
    );
  }
}

class WithdrawTransactionsTable extends React.Component {
  render() {
    return (
      <AppContext.Consumer>
        {context => (
          <div>
            <label>{context.jsonTransactions}</label>
          </div>
        )}
      </AppContext.Consumer>
    );
  }
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

<div id="root"></div>