javascript - 如何将 Redux 与 Reactjs 结合使用 - 以这个基本的 d3.js 图表为例

标签 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;

最佳答案

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

Redux

Redux (和 react-redux )用于管理应用程序的状态。它提供了一个中央存储,应保存渲染应用程序所需的所有数据和 mechanism当商店的状态发生变化时更新组件。

您的案例的功能流程是:

  1. 容器支架
  2. 它通过 AJAX 发出 API 调用
  3. 当调用返回时dispatch一个action ,例如dataReceived *
  4. 一个reducer通过更新商店来处理操作
  5. 您的容器/组件应该是 connected通过其 props 使用商店中的新数据进行更新
  6. 组件使用新数据重新渲染

[* 您通常还希望跟踪请求的进度,以便在执行请求之前进行调度和操作(例如 dataRequested ),也许 dataRequestFailed如果发生错误]

jQuery

你不需要它。

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

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

可以使用许多不同的库来发出 Ajax 请求。

D3

有一个few有关如何将 D3 与 React 集成的方法。 您可以让 D3 完成所有渲染,也可以使用人造 DOM 并使用 React 进行渲染。

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

最小 POC

我添加了上面描述的流程的简单演示

注释

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

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

mapDispatchToProps将操作绑定(bind)到 dispatch允许通过 this.props.<action_name> 在容器内轻松使用的方法.

reducer 是使用 switch 语句编写的,以说明 reducer 可以处理多个操作。

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

关于javascript - 如何将 Redux 与 Reactjs 结合使用 - 以这个基本的 d3.js 图表为例,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43544947/

相关文章:

javascript - jQuery 如果单选按钮被选中则继续下一个元素

reactjs - 如何在 React Router v5 中推送到历史记录?

javascript - Meteor 方法破坏订阅查询

javascript - d3.js:序数尺度

d3.js - Dimple.js 轴标签

javascript - 为什么这段使用匿名函数的代码不起作用

javascript - #linkTo 帮助程序中的动态 CSS 类字符串(使用 ember.js 版本 pre4)

javascript - 从中心缩放矩形元素

javascript - (new Array(x)).map 奇怪

javascript - 如何使用 react-spring 滚动到一个元素?