react实例-3点击增加+总数统计+redux_with_context

本文介绍了一个使用React和Redux实现的计数器应用程序,通过点击按钮可以增加或减少计数,同时显示总和。应用程序包括组件如Provider、ControlPanel、Counter、Summary等,实现了状态管理和UI更新。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 运行效果:点击加减,总数也同步更新
在这里插入图片描述
2代码
2.1 \src\Provider.js

import {PropTypes, Component} from 'react';

class Provider extends Component {

  getChildContext() {
    return {
      store: this.props.store
    };
  }

  render() {
    return this.props.children;
  }

}

Provider.propTypes = {
  store: PropTypes.object.isRequired
}

Provider.childContextTypes = {
  store: PropTypes.object
};

export default Provider;

2.2 \src\index.js

import React from 'react';
import ReactDOM from 'react-dom';
import ControlPanel from './views/ControlPanel';
import './index.css';

import store from './Store.js';
import Provider from './Provider.js';

ReactDOM.render(
  <Provider store={store}>
    <ControlPanel />
  </Provider>,
  document.getElementById('root')

\views\ControlPanel.js

import React, { Component } from 'react';
import Counter from './Counter.js';
import Summary from './Summary.js';

const style = {
  margin: '20px'
};

class ControlPanel extends Component {

  render() {
    return (
      <div style={style}>
        <Counter caption="First" />
        <Counter caption="Second" />
        <Counter caption="Third" />
        <hr/>
        <Summary />
      </div>
    );
  }
}

export default ControlPanel;

2.3 \views\Counter.js

import React, { Component, PropTypes } from 'react';

import * as Actions from '../Actions.js';

const buttonStyle = {
  margin: '10px'
};

class Counter extends Component {
  render() {
    const {caption, onIncrement, onDecrement, value} = this.props;

    return (
      <div>
        <button style={buttonStyle} onClick={onIncrement}>+</button>
        <button style={buttonStyle} onClick={onDecrement}>-</button>
        <span>{caption} count: {value}</span>
      </div>
    );
  }
}

Counter.propTypes = {
  caption: PropTypes.string.isRequired,
  onIncrement: PropTypes.func.isRequired,
  onDecrement: PropTypes.func.isRequired,
  value: PropTypes.number.isRequired
};


class CounterContainer extends Component {
  constructor(props, context) {
    super(props, context);

    this.onIncrement = this.onIncrement.bind(this);
    this.onDecrement = this.onDecrement.bind(this);
    this.onChange = this.onChange.bind(this);
    this.getOwnState = this.getOwnState.bind(this);

    this.state = this.getOwnState();
  }

  getOwnState() {
    return {
      value: this.context.store.getState()[this.props.caption]
    };
  }

  onIncrement() {
    this.context.store.dispatch(Actions.increment(this.props.caption));
  }

  onDecrement() {
    this.context.store.dispatch(Actions.decrement(this.props.caption));
  }

  onChange() {
    this.setState(this.getOwnState());
  }

  shouldComponentUpdate(nextProps, nextState) {
    return (nextProps.caption !== this.props.caption) ||
      (nextState.value !== this.state.value);
  }

  componentDidMount() {
    this.context.store.subscribe(this.onChange);
  }

  componentWillUnmount() {
    this.context.store.unsubscribe(this.onChange);
  }

  render() {
    return <Counter caption={this.props.caption}
      onIncrement={this.onIncrement}
      onDecrement={this.onDecrement}
      value={this.state.value} />
  }
}

CounterContainer.propTypes = {
  caption: PropTypes.string.isRequired
};

CounterContainer.contextTypes = {
  store: PropTypes.object
}

export default CounterContainer;

2.4 \ActionTypes.js

export const INCREMENT = 'increment';

export const DECREMENT = 'decrement';

2.5 \src\Actions.js

import * as ActionTypes from './ActionTypes.js';

export const increment = (counterCaption) => {
  return {
    type: ActionTypes.INCREMENT,
    counterCaption: counterCaption
  };
};

export const decrement = (counterCaption) => {
  return {
    type: ActionTypes.DECREMENT,
    counterCaption: counterCaption
  };
};

2.6 \src\Reducer.js

import * as ActionTypes from './ActionTypes.js';

export default (state, action) => {
  const {counterCaption} = action;

  switch (action.type) {
    case ActionTypes.INCREMENT:
      return {...state, [counterCaption]: state[counterCaption] + 1};
    case ActionTypes.DECREMENT:
      return {...state, [counterCaption]: state[counterCaption] - 1};
    default:
      return state
  }
}

2.7 \src\Store.js

import {createStore} from 'redux';
import reducer from './Reducer.js';

const initValues = {
  'First': 0,
  'Second': 10,
  'Third': 20
};

const store = createStore(reducer, initValues);

export default store;

2.8 \src\views\Summary.js

import React, { Component, PropTypes } from 'react';

class Summary extends Component {
  render() {
    const sum = this.props.sum;
    return (
      <div>Total Count: {sum}</div>
    );
  }
}

Summary.propTypes = {
  sum: PropTypes.number.isRequired
};


class SummaryContainer extends Component {
  constructor(props, context) {
    super(props, context);

    this.onChange = this.onChange.bind(this);

    this.state = this.getOwnState();
  }

  onChange() {
    this.setState(this.getOwnState());
  }

  getOwnState() {
    const state = this.context.store.getState();
    let sum = 0;
    for (const key in state) {
      if (state.hasOwnProperty(key)) {
        sum += state[key];
      }
    }

    return { sum: sum };
  }

  shouldComponentUpdate(nextProps, nextState) {
    return nextState.sum !== this.state.sum;
  }

  componentDidMount() {
    this.context.store.subscribe(this.onChange);
  }

  componentWillUnmount() {
    this.context.store.unsubscribe(this.onChange);
  }

  render() {
    const sum = this.state.sum;
    return (
      <Summary sum={sum} />
    );
  }
}

SummaryContainer.contextTypes = {
  store: PropTypes.object
}

export default SummaryContainer;

代码分析

D:\AwesomeProject>npm ls react AwesomeProject@0.0.1 D:\AwesomeProject +-- @ant-design/icons-react-native@2.3.2 | `-- react@17.0.2 deduped +-- @ant-design/react-native@5.4.2 | +-- @floating-ui/react-native@0.10.7 | | `-- react@17.0.2 deduped | +-- rc-field-form@2.7.0 | | `-- react@17.0.2 deduped | +-- rc-util@5.44.4 | | `-- react@17.0.2 deduped | +-- react-native-collapsible@1.6.2 | | `-- react@17.0.2 deduped | +-- react-native-modal-popover@2.1.3 | | `-- react@17.0.2 deduped | `-- react@17.0.2 deduped +-- @react-native-community/cameraroll@4.1.2 | `-- react@17.0.2 deduped +-- @react-native-community/masked-view@0.1.11 | `-- react@17.0.2 deduped +-- @react-native-community/segmented-control@2.2.2 | `-- react@17.0.2 deduped +-- @react-native-community/slider@3.0.3 | `-- react@17.0.2 deduped +-- @react-native-community/viewpager@4.2.4 | `-- react@17.0.2 deduped +-- @react-native-picker/picker@1.16.8 | `-- react@17.0.2 deduped +-- @react-navigation/native-stack@6.11.0 | +-- @react-navigation/elements@1.3.31 | | `-- react@17.0.2 deduped | `-- react@17.0.2 deduped +-- @react-navigation/native@6.1.18 | +-- @react-navigation/core@6.4.17 | | +-- react@17.0.2 deduped | | `-- use-latest-callback@0.2.4 | | `-- react@17.0.2 deduped invalid: "^16.0.0" from node_modules/react-native-elements/node_modules/react-native-ratings | `-- react@17.0.2 deduped +-- @rneui/base@4.0.0-rc.8 | `-- react-native-ratings@8.1.0 | `-- react@17.0.2 deduped +-- react-native-elements@1.2.7 | `-- react-native-ratings@6.5.0 | `-- react@17.0.2 deduped invalid: "^16.0.0" from node_modules/react-native-elements/node_modules/react-native-ratings +-- react-native-exception-handler@2.10.10 | `-- react@17.0.2 deduped +-- react-native-image-picker@3.8.1 | `-- react@17.0.2 deduped +-- react-native-image-zoom-viewer@3.0.1 | +-- react-native-image-pan-zoom@2.1.12 | | `-- react@17.0.2 deduped invalid: "^16.0.0" from node_modules/react-native-elements/node_modules/react-native-ratings | `-- react@17.0.2 deduped +-- react-native-linear-gradient@2.8.3 | `-- react@17.0.2 deduped +-- react-native-permissions@3.8.0 | `-- react@17.0.2 deduped +-- react-native-reanimated@2.14.4 invalid: ">=3.10.1" from node_modules/@ant-design/react-native | `-- react@17.0.2 deduped +-- react-native-safe-area-context@3.3.2 | `-- react@17.0.2 deduped +-- react-native-screens@3.15.0 | +-- react-freeze@1.0.4 | | `-- react@17.0.2 deduped invalid: "^16.0.0" from node_modules/react-native-elements/node_modules/react-native-ratings | `-- react@17.0.2 deduped +-- react-native-svg-charts@5.4.0 | `-- react@17.0.2 deduped +-- react-native-webview@11.26.1 | `-- react@17.0.2 deduped +-- react-native@0.68.0 | +-- react-shallow-renderer@16.14.1 | | `-- react@17.0.2 deduped invalid: "^16.0.0" from node_modules/react-native-elements/node_modules/react-native-ratings | +-- react@17.0.2 deduped | `-- use-subscription@1.11.0 | +-- react@17.0.2 deduped invalid: "^16.0.0" from node_modules/react-native-elements/node_modules/react-native-ratings | `-- use-sync-external-store@1.5.0 | `-- react@17.0.2 deduped invalid: "^16.0.0" from node_modules/react-native-elements/node_modules/react-native-ratings +-- react-redux@7.2.9 | `-- react@17.0.2 deduped +-- react-test-renderer@17.0.2 | `-- react@17.0.2 deduped `-- react@17.0.2 npm error code ELSPROBLEMS npm error invalid: react@17.0.2 D:\AwesomeProject\node_modules\react npm error A complete log of this run can be found in: C:\Users\临时\AppData\Local\npm-cache\_logs\2025-08-01T01_11_42_653Z-debug-0.log
最新发布
08-02
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值