【问题标题】:How to simulate events using React-Redux?如何使用 React-Redux 模拟事件?
【发布时间】:2017-04-29 02:55:22
【问题描述】:

我正在使用 React 和 Electron 构建一个桌面应用程序。 由于它发展迅速,我意识到我需要一些像 Redux 这样的状态管理来避免在组件之间传递许多属性。 我开始阅读 Redux 官方 documentation 但仍然无法弄清楚如何在我的情况下实现它。我被困住了!

例如,我有一个主要的App 组件,它渲染了许多子组件。其中一个有一个按钮。单击时,它应该向商店发送一个“事件”,以便主App 可以采取相应的行动。我怎样才能做到这一点? 我找不到事件的概念,而且我在如何开始使用 Redux 方面遇到了困难。

为什么是事件?因为在这种情况下调度一个动作并修改应用程序状态对我来说似乎很愚蠢。我只想通知根组件根据用户操作分派一个操作。 用户与展示组件交互,该组件应该告诉容器组件进行 API 调用或开始捕获音频/摄像头等。

就我目前所知,实现这一点的唯一方法是改变状态,以便另一个监听变化的组件检测到一个特殊值,表示“嘿,让我们这样做”,然后再次改变状态以说“嘿,我正在这样做”,当它完成时,状态会再次更改为“嘿,它完成了”。

有人能指点我正确的方向吗?

【问题讨论】:

    标签: javascript reactjs events redux store


    【解决方案1】:

    用户与展示组件交互,该组件应该告诉容器组件进行 API 调用或开始捕获音频/摄像头等。

    也许你的容器组件做得比它应该做的多。考虑一下 React 组件只做两件事的情况:

    1. 根据 props 显示 DOM 元素
    2. 处理用户输入(调度事件)

    如果您没有使用 redux,并且想在单击按钮时进行 API 调用,则可能类似于:

    class App extends Component {
      state = { data: {} }
    
      makeAPICall() {
        fetch(url).then(data => this.setState({ data }))
      }
    
      render() {
        <Child 
          data={this.state.data} 
          makeAPICall={this.makeAPICall} 
        />
      }
    }
    
    let Child = ({ data, makeAPICall }) => (
      <button onClick={makeAPICall}>Call API!</button>
    )
    

    App 组件负责存储全局状态和处理事件,但我们必须通过组件树传递该状态和 App 的处理程序,很可能通过本身永远不会使用这些道具的组件。

    通过添加 Redux,您的应用程序现在可以更好地处理 API 调用或打开相机等副作用。中间件!

    让这个(蹩脚的)插图帮助你:

    因此,现在您的 App 组件可以像所有其他组件一样只是一个普通的展示组件,只需根据存储道具显示数据并在需要时处理任何用户输入/调度操作。让我们使用thunk 中间件更新上面的例子

    // actions.js
    export let makeAPICall = () => {
      return dispatch => {
        fetch(url).then(data => dispatch({ 
          type: 'API_SUCCESS',
          payload: data,
        })).catch(error => dispatch({ type: 'API_FAIL', payload: error }))
      }
    }
    
    // Child.js
    import { connect } from 'react-redux'
    import { makeAPICall } from './actions'
    
    let Child = ({ dispatch }) => (
      <button onClick={() => dispatch(makeAPICall())}>Call API!</button>
    )
    
    export default connect()(Child)
    

    以这种方式思考 React 应用程序非常强大。关注点分离的布局非常好。组件显示内容并处理事件。中间件负责处理任何副作用(如果需要的话),而 store 只是一个对象,它会导致 React 在其数据发生变化时重新渲染。

    更新:“模态问题”

    React 应用可能有一些全局性的东西,比如模态和工具提示。不要考虑“打开模态”事件。想想“当前模态内容是什么?”。

    模态设置可能看起来像这样:

    // modalReducer.js
    function reducer (state = null, action) {
       if (action.type === 'UPDATE_MODAL') {
         return action.payload
       } 
    
       // return default state
       return state
    }
    
    // App.js
    let App = connect(state => ({ modal: state.modal }))(
      props => 
        <div>
           <OtherStuff />
           <Modal component={props.modal} />
        </div>
    )
    
    // Modal.js
    let Modal = props =>
      <div
        style={{ 
          position: 'fixed',
          width: '100vw', height: '100vh',
          opacity: props.component ? 1 : 0,
        }}
      >
        {props.component}
      </div>
    
    // Child.js
    
    let Child = connect()(props =>
      <button onClick={e =>
        dispatch({ 
          type: 'UPDATE_MODAL'
          payload: <YourAwesomeModal />
        })
      }>
        Open your awesome modal!
      </button>
    )
    

    这只是一个示例,但效果很好!当state.modalnull 你的模态有0 不透明度并且不会显示。当您发送 UPDATE_MODAL 并传入一个组件时,模式将显示您发送的任何内容并将不透明度更改为 1,以便您可以看到它。稍后您可以发送{ type: 'UPDATE_MODAL', payload: null } 以关闭模式。

    希望这能让你思考一些事情!

    绝对是read this answer by Dan。他的方法类似,但存储了模态“元数据”而不是组件本身,这更适合 Redux 的幻想,比如时间旅行等。

    【讨论】:

    • 这确实有帮助,但不能解决我的问题。我有一个模态对话框,它是根 App 组件的一部分,一个子子子子组件有一个按钮,单击该对话框时需要显示该按钮。如果没有事件的概念,也没有一直向下和向上传递事件处理程序,我如何实现这一点?
    • 啊,我最喜欢的问题之一!添加这个。
    • 中间件是您需要监听动作并以某种方式响应的工具。也就是说,Dan Abramov 在managing modal dialogs using Redux 上写了一个 SO 答案,我写了一篇基于他处理receiving responses from generic dialogs 的技术的博客文章。
    • 我更喜欢在使用它的组件中使用模态对话框。不过没关系。所以你说 API 调用和其他逻辑东西应该放在异步操作中?仍然不知道如何让组件知道用户点击了一个按钮,有很多组件在路上,不像你的例子。
    • 使用connect 时有多少组件并不重要。直接连接你要访问商店的组件即可
    【解决方案2】:

    您认为这看起来很愚蠢的原因是因为您不希望您的展示组件支持 redux 吗?如果是这样,mapDispatchToPropsbindActionCreators 可能有助于整理,例如:

    // App.js
    import React from 'react';
    import { bindActionCreators } from 'redux';
    import { connect } from 'react-redux';
    import { someAction } from './actions';
    import Button from './Button';
    
    const App = ({ onButtonClick }) => (
        <div>
            Hello.
            <Button onClick={onButtonClick}>Click me.</Button>
        </div>
    );
    
    export default connect(null, dispatch => {
        return bindActionCreators({
            onButtonClick: someAction
        }, dispatch);
    })(App);
    
    // Button.js
    import React from 'react';
    
    export default Button = ({ onClick, children }) => <button onClick={onClick}>{children}</button>;
    

    正如您所见,只有连接的容器组件知道该操作,Button(甚至应用程序)不知道点击触发了操作。

    【讨论】:

    • 您是否忘记在 connect() 调用中导出 App?请看我上面的评论。我想避免在主状态树中有一个属性,上面写着“让我们显示一个对话框”,然后在对话框打开时很快就需要删除它......这很愚蠢。我可以观察动作并检测组件何时想以某种方式打开对话框吗?
    【解决方案3】:

    对于它的价值,我遇到了类似的问题(单击树中其他地方的按钮并导致地图重置其视口)并使用简单的增量键解决了它。

    按钮调度动作:

    export const RESET_MAP = "RESET_MAP";
    export const resetMap = () => {
      return {
        type: RESET_MAP,
      };
    };
    

    在减速器中:

    case RESET_MAP:
      return Object.assign({}, state, {
        setvar: state.setvar + 1
      });
    

    在地图中:

    static getDerivedStateFromProps(newProps, state) {
      var newState = null;
    
      if (newProps.setvar !== state.setvar) {
          newState = {
            setvar: newProps.setvar,
            [other magic to reset the viewport]
          }
      }
    
      return newState;
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-12-04
      • 1970-01-01
      • 2018-12-16
      • 1970-01-01
      • 2022-11-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多