如何将Redux与Reactjs一起使用 - 这个基本的d3.js图表​​作为示例

时间:2017-04-21 14:13:36

标签: javascript reactjs d3.js redux

我是Reactjs的新手 - 主要是通过整合Redux。我很想学习如何改进下面的代码库以使用Redux - 所以要了解这个当前示例有什么问题以及如何完全清理它 - 添加Redux - 并解释为什么要使用Redux及其主要目的。

所以这里是饼图的一些测试json数据

var data = [{
                    "label": "Belleville Brewing Company",
                    "value": 1233
                  }, {
                    "label": "Kew Brewery",
                    "value": 345
                  }, {
                    "label": "Laines Brewery (Four Thieves)",
                    "value": 6786
                  }, {
                    "label": "Sultan Brewery",
                    "value": 678
                  }, {
                    "label": "The Wimbledon Brewery Company Limited",
                    "value": 45
                  }];

// index.js

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

var $ = require("jquery");

import PieChart from './modules/7_pieChart/PieChart';   


var MultipleComponents = React.createClass({

    getInitialState: function() {
      return {
        username: '',
        lastGistUrl: '',
        rawData: '',
        config: ''
      };
    },

    componentDidMount: function () {


      var config = [];

        this.serverRequest = $.get(this.props.source, function (result) {
          var lastGist = result[0];
          this.setState({
            username: lastGist.owner.login,
            lastGistUrl: lastGist.html_url,
            rawData: lastGist,
            config: config
          });
        }.bind(this));
    },

    componentWillUnmount: function() {
      this.serverRequest.abort();
    },


    getLayers: function(data){
      var items = [];
      var j = 0;


      items.push(  <PieChart 
                      key="5"
                      width="350" 
                      height="350" 
                      radius="200" 
                      innerradius="120" 
                      serviceApi=""/> );

      return items;      
    },


    render: function () {
       var config = this.state.config;

       console.log("config", config);

       return (
            <div className="apps">
                {this.getLayers(config[0])}
            </div>
        );
    }
});




ReactDOM.render(
    <MultipleComponents source="https://api.github.com/users/octocat/gists" />,
    document.getElementById('root')
);

//饼图js

    import React, { Component } from 'react';
    import ReactDOM from 'react-dom';
    var $ = require("jquery");
    var d3 = require("d3");
    import './PieChart.css';

    class PieChart extends Component {
        componentDidMount() {
            var $this = $(ReactDOM.findDOMNode(this));

            var data = [{
                "label": "Belleville Brewing Company",
                "value": 1233
              }, {
                "label": "Kew Brewery",
                "value": 345
              }, {
                "label": "Laines Brewery (Four Thieves)",
                "value": 6786
              }, {
                "label": "Sultan Brewery",
                "value": 678
              }, {
                "label": "The Wimbledon Brewery Company Limited",
                "value": 45
              }];


            var w = $this.data("width");
            var h = $this.data("height");
            var ir = $this.data("innerradius");
            var r = $this.data("radius");

            function colores_google(n) {
                var colores_g = ["#f7b363", "#448875", "#c12f39", "#2b2d39", "#f8dd2f"];
                //var colores_g = ["#47abd5", "#005a70", "#f5a0a3", "#ff7276", "#a9a19c", "#d0743c", "#ff8c00"];
                return colores_g[n % colores_g.length];
            }

            var radius = Math.min(w, h) / 3;


            var arc = d3.svg.arc()
                .outerRadius(radius - 10)
                .innerRadius(0);

            var labelArc = d3.svg.arc()
                .outerRadius(radius - r)
                .innerRadius(radius - ir);    

            var pie = d3.layout.pie()
                .sort(null)
                .value(function(d) { return d.value; });


            var chart = d3.select($this[0]).append("svg:svg")
                            .attr("class", "chart")
                            .attr("width", w - (w/3))
                            .attr("height", h)
                                .append("svg:g")
                                .attr("class", "piechart")
                                .attr("transform", "translate(20,"+h/3+")");

            var path_group = chart.append("svg:g")
                .attr("class", "path_group")
                .attr("transform", "translate(90," + ((h / 4) - 20) + ")");


            var padding = 45;
            var legendPaddingTop = 30;
            var legend = d3.select($this[0]).append("svg:svg")
                .attr("class", "legend")
                .attr("width", w/2)
                .attr("height", h)
                .append("svg:g")
                .attr("class", "legendsection")
                .attr("transform", "translate(" + ((w/4) + padding) + "," + legendPaddingTop + ")");    


            var label_group = legend.append("svg:g")
                .attr("class", "label_group")
                .attr("transform", "translate(" + (-(w / 3) + 20) + "," + 0 + ")");

            var legend_group = legend.append("svg:g")
                .attr("class", "legend_group")
                .attr("transform", "translate(" + (-(w / 3) - 100) + "," + 0 + ")");


            var g = path_group.selectAll(".arc")
                .data(pie(data))
                .enter().append("g")
                .attr("class", "arc");

            g.append("path")
                .attr("d", arc)
                .style("fill", function(d, i) { 
                    return colores_google(i);
                });


            var legendHeight = legendPaddingTop;
            var ySpace = 18;
            var labelPadding = 3;

            //draw labels                   
            var labels = label_group.selectAll("text.labels")
                .data(data);

            labels.enter().append("svg:text")
                .attr("class", "labels")
                .attr("dy", function(d, i) {                  
                    legendHeight+=ySpace;   
                  return (ySpace * i) + labelPadding;
                })
                .attr("text-anchor", function(d) {
                  return "start";
                })
                .text(function(d) {
                  return d.label;
                });

            labels.exit().remove();
            //draw labels


            //draw legend
            var legend = legend_group.selectAll("circle").data(data);

            legend.enter().append("svg:circle")
                .attr("cx", 100)
                .attr("cy", function(d, i) {
                  return ySpace * i;
                })
                .attr("r", 7)
                .attr("width", 18)
                .attr("height", 18)
                .style("fill", function(d, i) {
                  return colores_google(i);
                });

            legend.exit().remove();
            //draw legend

            //reset legend height
            //console.log("optimum height for legend", legendHeight);
            $this.find('.legend').attr("height", legendHeight);

            function type(d) {
              d.value = +d.value;
              return d;
            }

        }

        render() {
            return (
                <div className="piechart" data-role="piechart" data-width={this.props.width} data-height={this.props.height} data-radius={this.props.radius}  data-innerradius={this.props.innerradius}
                    data-data={this.props.data}>
                </div>
            );
        }
    };

    export default PieChart;

1 个答案:

答案 0 :(得分:2)

我将解决您发布的代码的几个问题。这里的目的是提供一个指南,而不是深入解释每个部分。

终极版

Redux(和react-redux)用于管理应用程序的状态。它提供了一个中央存储,它应该包含呈现应用程序所需的所有数据,以及mechanism在商店状态发生变化时更新组件。

您案例中的功能流程为:

  1. 容器装载
  2. 它通过AJAX发出API调用
  3. 当通话返回dispatchaction时,例如dataReceived *
  4. a reducer通过更新商店来处理操作
  5. 应该是connected的容器/组件使用商店中的新数据通过其道具进行更新
  6. 使用新数据重新呈现组件
  7. [*您通常也希望跟踪请求的进度,因此您将在执行请求之前发送和执行操作(例如dataRequested),如果发生错误,则可能dataRequestFailed

    的jQuery

    你不需要它。

    应该使用React将props传递给子组件并更新DOM。

    为什么要通过DOM传递宽度和高度道具,而不是直接通过渲染代码中的this.props访问它们?

    Ajax请求可以通过许多不同的库来完成。

    D3

    有关如何将D3与React集成的few方法。 您可以让D3完成所有渲染或使用faux-DOM并使用React渲染它。

    您发布的代码使用D3进行渲染,但仅在componentDidMount方法中进行渲染。您还应该挂钩componentDidUpdate方法,以便将更新的道具传递给D3。 Here是关于如何实现这一目标的一篇很好的文章。

    最小POC

    我添加了一个关于上面描述的流程的简单演示

    备注

    connect用于创建订阅商店中的更改的组件,并在商店更改时自动更新。

    mapStateToProps定义应将商店的哪些属性传递给容器。

    mapDispatchToProps将操作绑定到dispatch方法,以便通过this.props.<action_name>在容器内轻松使用。

    reducer是使用switch语句编写的,用于说明reducers可以处理多于1个动作的事实。

    // reducers.js
    const reducer = (state = {data: []}, action) => {
      switch(action.type) {
        case 'DATA_RECEIVED':
         return {...state, data: [...state.data, action.payload] }
       }
      return state;
    }
    
    // store.js
    const store = Redux.createStore(reducer)
    
    // actions.js
    function dataReceived(payload) {
      console.log('dataReceived action: ', payload);
      return {
        type: 'DATA_RECEIVED',
        payload,
      }
    }
    
    // pie_chart.js
    class PieChart extends React.Component {
      
      _update() {
        const svg = d3.select(this.node).select('svg')
          .attr('width', this.props.width)
          .attr('height', this.props.height)
        
        const y = d3.scale.ordinal().domain(this.props.data)
          .rangePoints([20, this.props.height - 10])
        
        const textItems = svg.selectAll('text').data(this.props.data)
        
        textItems.enter().append('text').text(d => d)
        
        textItems.transition().attr('y', (d, i) => y(d))
        
        textItems.exit().remove()
      }
      
      componentDidMount() {
        const svg = d3.select(this.node)
          .append('svg')
          
        this._update()
      }
      
      componentDidUpdate() {
        this._update()
      }
      
      render() {
        return (
          <div class="container" ref={node => this.node = node}/>
        )
      }
    }
    
    // chart_container.js
    function mapStateToProps(state) { 
      return { data: state.data }
    }
    
    function mapDispatchToProps(dispatch) {
      return {
        dataReceived: Redux.bindActionCreators(dataReceived, dispatch),
      }
    }
    
    const ChartContainer = ReactRedux.connect(mapStateToProps, mapDispatchToProps)(class ChartContainer extends React.Component {
      componentDidMount() {
        let items = 3
        
        const active = setInterval(() => {     // simulate API call
          this.props.dataReceived('data item ' + items);
          if (items < 1) { 
            clearInterval(active)
          }
          items -= 1
          
        }, 3000)
      }
      render() {
        return (
          this.props.data.length > 0 ? 
          <PieChart width={100} height={100} data={this.props.data} /> :
          <div>no data yet</div>
          
        )
      }
    })
    
    // app.js
    ReactDOM.render(
      <ReactRedux.Provider store={store}>
        <ChartContainer />
      </ReactRedux.Provider>,
      document.getElementById('root')
    )
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/redux/3.6.0/redux.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/5.0.4/react-redux.js"></script>
    <div id="root"></div>