简体   繁体   English

如何使用 React-Redux 模拟事件?

[英]How to simulate events using React-Redux?

I'm building a desktop app using React and Electron.我正在使用 React 和 Electron 构建一个桌面应用程序。 Since it's growing fast, I realized I need some kind of state management like Redux to avoid passing many properties between components.由于它增长很快,我意识到我需要某种像 Redux 这样的状态管理来避免在组件之间传递许多属性。 I started reading Redux official documentation but still cannot figure out how to implement it in my case.我开始阅读 Redux 官方文档,但仍然无法弄清楚如何在我的情况下实现它。 I'm stuck!我被卡住了!

For example, I have a main App component that renders many sub-components.例如,我有一个主要的App组件,它呈现了许多子组件。 One of them has a button.其中之一有一个按钮。 When clicked, it should dispatch an "event" to the store so the main App can act in consequence.单击时,它应该向商店发送一个“事件”,以便主App可以采取行动。 How can I accomplish that?我怎样才能做到这一点? I cannot find the concept of events and I've hit a wall on how to even start using Redux.我找不到事件的概念,我什至在如何开始使用 Redux 方面遇到了障碍。

Why events?为什么是事件? Because it seems silly to me to dispatch an action and modify app state in this case.因为在这种情况下分派动作并修改应用程序状态对我来说似乎很愚蠢。 I just want to inform the root component to dispatch an action based on a user action.我只想通知根组件根据用户操作调度操作。 User interacts with a presentational component that should tell a container component to make an API call or start capturing audio/camera for example.用户与演示组件交互,该组件应该告诉容器组件进行 API 调用或开始捕获音频/摄像头。

For what I know up to now, the only way to accomplish this is to mutate state so another component listening for changes detects a special value that means "hey, let's do this", then mutate state again to say "hey, I'm doing this", and when it's done state changes again with "hey, it's done".就我目前所知,实现此目的的唯一方法是改变状态,以便另一个侦听更改的组件检测到一个特殊值,这意味着“嘿,让我们这样做”,然后再次改变状态以说“嘿,我是这样做”,当它完成时,状态再次改变为“嘿,它完成了”。

Can someone point me in the right direction please?有人可以指出我正确的方向吗?

User interacts with a presentational component that should tell a container component to make an API call or start capturing audio/camera for example.用户与演示组件交互,该组件应该告诉容器组件进行 API 调用或开始捕获音频/摄像头。

Perhaps your container component is doing more than it should.也许您的容器组件做得比它应该做的要多。 Consider a situation where React components do no more than two things:考虑 React 组件只做两件事的情况:

  1. Display DOM elements based on props根据 props 显示 DOM 元素
  2. Handle user input (dispatch events)处理用户输入(调度事件)

If you were not using redux and wanted to make an API call when clicking a button, that might look something like:如果您没有使用 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>
)

The App component is responsible for storing global state and handling events, but we have to pass down that state and App's handlers through the component tree, quite possibly through components that will never themselves use those props. App组件负责存储全局状态和处理事件,但我们必须通过组件树传递该状态和 App 的处理程序,很可能通过组件本身永远不会使用这些道具。

By adding Redux your application now has a much better place to handle side effects like API calls or turning a camera on.通过添加 Redux,您的应用程序现在可以更好地处理 API 调用或打开相机等副作用。 Middleware!中间件!

Let this (crappy) illustration help you:让这个(蹩脚的)插图帮助你:

在此处输入图片说明

So now instead your App component can be just a normal presentational component like all of the others, simply displaying data based on store props and handling any user input / dispatching actions if need be.因此,现在您的App组件可以像所有其他组件一样只是一个普通的展示组件,只需显示基于商店道具的数据,并在需要时处理任何用户输入/调度操作。 Let's update the above example using the thunk middleware让我们使用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)

Thinking about React applications this way is very powerful.以这种方式思考 React 应用程序非常强大。 The separation of concerns is very well laid out.关注点分离的布局非常好。 Components display stuff and handle events.组件显示内容并处理事件。 Middleware takes care of any side effects (if there need to be any) and the store simply is an object that will cause React to re-render in case its data changes.中间件负责处理任何副作用(如果需要的话),而 store 只是一个对象,如果它的数据发生变化,它将导致 React 重新渲染。

UPDATE: "The Modal Problem"更新:“模态问题”

React apps may have some global stuff like modals and tooltips. React 应用程序可能有一些全局的东西,比如模态和工具提示。 Don't think about the "open modal" event.. think "what is the current modal content?".不要考虑“打开模态”事件..想“当前模态内容是什么?”。

A modal setup may look something along these lines:模态设置可能看起来像以下内容:

// 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>
)

This is just an example, but would work great!这只是一个例子,但会很好用! when state.modal is null your Modal has 0 opacity and won't show.state.modalnull您的 Modal 具有 0 不透明度并且不会显示。 When you dispatch UPDATE_MODAL and pass in a component, the modal will show whatever you dispatch and change the opacity to 1 so you can see it.当您调度UPDATE_MODAL并传入一个组件时,模态将显示您调度的任何内容并将不透明度更改为 1,以便您可以看到它。 Later you can dispatch { type: 'UPDATE_MODAL', payload: null } to close the modal.稍后您可以调度{ type: 'UPDATE_MODAL', payload: null }关闭模态。

Hopefully this gives you some things to think about!希望这能让你思考一些事情!

Definitely read this answer by Dan .一定要阅读 Dan 的这个答案 His approach is similar but stored modal "metadata" vs the component itself which lends itself better to Redux fanciness like time travel etc.他的方法是相似的,但存储的模态“元数据”与组件本身相比,这更适合于时间旅行等 Redux 幻想。

Is the reason you think it seems silly because you don't want your presentational components to be redux-aware?您认为这看起来很愚蠢的原因是因为您不希望您的展示组件具有 redux-aware 吗? If so mapDispatchToProps and bindActionCreators might help tidy things up, for example:如果是这样, 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>;

As you can see only the connected container component is aware of the action, the Button (and even the App) are unaware that click triggers an action.正如您所看到的,只有连接的容器组件知道操作,按钮(甚至应用程序)不知道单击会触发操作。

For what it's worth, I had a similar problem (click a button elsewhere in the tree and cause a map to reset its viewport) and solved it with a simple incremental key.对于它的价值,我遇到了类似的问题(单击树中其他位置的按钮并导致地图重置其视口)并使用简单的增量键解决了它。

Button dispatches action:按钮调度动作:

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

In reducer:在减速机中:

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

In map:在地图中:

static getDerivedStateFromProps(newProps, state) {
  var newState = null;

  if (newProps.setvar !== state.setvar) {
      newState = {
        setvar: newProps.setvar,
        [other magic to reset the viewport]
      }
  }

  return newState;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM