最新最全的
KendoUI技术文章

当 React 遇上 KendoUI

注:React.js 是 Facebook 推出的一个用来构建用户界面的 JavaScript 库。

ReactJS从根本上改变了我对待Web界面的态度。在短期接触ReactJS的时间里,它带给了我从未体会过的惊喜。

让人兴奋的根源,我想是React对待UI的方式和我们之前接触过的不太一样。你不需要直接操作DOM,Readct的组件会渲染一个可视化的DOM。这个可视化的DOM会将它的改变展现出来,并会计算出更新所需要的最少步骤(然后真正地更新了DOM),这一点仅仅是作用在DOM需要改变的部分上(想要了解React reconciliation的机制,请看这里这里)。

到目前为止我使用过的库或者框架都是直接操作DOM的,如果我不想放弃已经在使用UI部件,又想要使用React,我也能这么做吗?

你当然可以咯

当React遇上KendoUI

为了说明这一点,让我们用KendoUI构建一个计量器(Radial Gauge,它还包含了KendoUI slider)来演示,并将它转换成React组件。你可以点击上面的链接,看一看使用KendoUI和jQuery是如何写出这个效果的(这是非React的方式)。(这里我要提醒大家一下,Radial Gauge是KendoUI专业版的功能,这个版本是需要支付费用的,比起开源版的KendoUI core专业版提供了很多复杂的部件或可视化效果。你可以看看这篇文章来对比这两个版本。)

Radial Gauge

首先,创建一个RadiaGauge组件,它可以在DOM中渲染出一个div(这个步骤执行完毕之后,KendoUI部件将会锁定这个元素):

1
2
3
4
5
6
7
8
 // react 0.11 supports namespaced components :)
 var Kendo = {};
 Kendo.RadialGauge = React.createClass({  
   render: function() {
     return <div className="gauge" />;
   }
 });

不要担心,它不会停在那儿的:)。

React组件有一些生命周期方法(lifecycle methods),我们将会用到的是componentDidMount。当一个组件第一次被渲染到页面上时,这个方法就会被调用,这时我们就可以操作DOM了。我们可以使用getDOMNode来获取刚刚那个div的引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 Kendo.RadialGauge = React.createClass({  
  componentDidMount: function() {
    $(this.getDOMNode()).kendoRadialGauge({
      theme: "silver",
      pointer: {
        value: 88
      },    
      scale: {
        minorUnit: 5,
        startAngle: -30,
        endAngle: 210,
        max: 180
      }
    });
  },
  render: function() {
    return <div className="gauge" />;
  }
});

好吧,上面的代码看起来糟糕透了,组件的配置完全是硬编码。我们其实可以将这个配置的值用props来传递,默认值可以用getDefaultProps来使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
 // just showing the relevant methods
 Kendo.RadialGauge = React.createClass({  
   getDefaultProps: function() {
     return {
       theme: "silver",
       minorUnit: 5,
       startAngle: -30,
       endAngle: 210,
       max: 180
     }
   },
   componentDidMount: function() {
     var props = this.props;
     $(this.getDOMNode()).kendoRadialGauge({
       theme: props.theme,
       pointer: {
         value: props.value
       },    
       scale: {
         minorUnit: props.minorUnit,
         startAngle: props.startAngle,
         endAngle: props.endAngle,
         max: props.max
       }
     });
   },
   // other methods, etc.
 });

现在看起来好多了,不过还有最后一道坎要跨。

“有一点很重要:因为我们是在组件渲染之后才开始操作DOM的,KendoUI部件是没法使用ReactJS分析DOM前后不同这个功能。所以我们需要监听部件的改变好及时作出反应。”

我们需要监听props.value的值是否被改变,如果改变了就要重新渲染部件。componentWillReceivePropscomponentDidUpdate这两个方法都能对props.value的改变做出反应。如果我们只是简单的重新渲染KendoUi radial gauge,那就没有办法将那个改变的过程动画化。所以目前我们是这么做的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
 Kendo.RadialGauge = React.createClass({  
   getDefaultProps: function() {
     return {
       theme: "silver",
       minorUnit: 5,
       startAngle: -30,
       endAngle: 210,
       max: 180
     };
   },
   componentDidMount: function() {
     var props = this.props;
     $(this.getDOMNode()).kendoRadialGauge({
       theme: props.theme,
       pointer: {
         value: props.value
       },    
       scale: {
         minorUnit: props.minorUnit,
         startAngle: props.startAngle,
         endAngle: props.endAngle,
         max: props.max
       }
     });
   },
   componentWillReceiveProps: function(nextProps) {
     if(nextProps.value !== this.props.value) {
       $(this.getDOMNode()).data("kendoRadialGauge").value(nextProps.value);
     }
   },
   render: function() {
     return <div className="gauge" />;
   }
 });

componentWillReceiveProps中,如果发现部件发生改变,那么部件中的某个值会变化,KendoUI会动画化这个改变的过程。

从React角度上来看,RadialGauge组件就是渲染出一个div,所以监听它的props改变只是微不足道的性能消耗。(对于componentWillReceiveProps来说,DOM的改变不一定会触发KendoUI部件的更新。)但是如果你的组件渲染方法比较复杂,那最好还是要有shouldComponentUpdate

Slider

现在让我们给轮播创建一个组件。第一步:渲染一个div,在componentDidMount调用这个组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 Kendo.Slider = React.createClass({  
   getDefaultProps: function() {
     return {
       min: 0,
       max: 180,
       showButtons: false
     };
   },
   componentDidMount: function() {
     var props = this.props;
     $(this.getDOMNode()).kendoSlider({
       min: props.min,
       max: props.max,
       showButtons: props.showButtons,
       value: this.props.value
     });
   },
   render: function() {
     return <div />;
   }
 });

这里有一点不同。

事件(Events)

我们需要处理由于用户的输入导致轮播的值出现变化的情况。因为现在处理的是一个由React合成事件系统(synthetic events system)产生的事件,我们需要提供一个事件处理程序给这个KendoUI部件。还有一点要注意:如果这个组件从DOM中被移除了,那相应的事件处理程序也要移除掉,这个可以让componentWillUnmount来做。

混合(Mixins)

在Slider组件中,我还用到了React的混合(mixins)。当一个子控件要通知其他控件它的值发生改变的时候,可选的方法并不多。我使用的是messaging mixin,这个方法可以使得我的组件值发生改变的时候告诉给其他组件(其他组件要监听这个值):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
 Kendo.Slider = React.createClass({
   mixins: [React.postal],
   getDefaultProps: function() {
     return {
       min: 0,
       max: 180,
       showButtons: false
     };
   },
   componentDidMount: function() {
     var props = this.props;
     $(this.getDOMNode()).kendoSlider({
       min: props.min,
       max: props.max,
       showButtons: props.showButtons,
       value: this.props.value,
       change: this.handleChange
     });
   },
   componentWillUnmount: function() {
     $(this.getDOMNode()).data("kendoSlider").destroy();
   },
   handleChange: function(e) {
     this.publish("change.speed", { speed: e.value });
   },
   render: function() {
     return <div />;
   }
 });

请注意,我们在componentWillUnmount方法中调用了destroy(),这一步很重要。

再多说一点

难道就停在gauge和slider这里了吗?我想要用“传统的”React组件来展示这个,所以现在你会在页面上看到一个下拉菜单,菜单中的两个选项是两种不同的测量方式。

UnitOfMeasure Component

UnitOfMeasure组件使用了messaging mixin来展现所选择的值的改变。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 var UnitOfMeasure = React.createClass({  
   mixins: [React.postal],
   handleChange: function(e) {
     this.publish("change.uom", {
       uom: $(this.getDOMNode()).val()
     });
   },
   render: function() {
     return <select className="uom-select" onChange={this.handleChange}>
       <option>MPH</option>
       <option>KPH</option>
     </select>;
   }
 });

SpeedDisplay Component

这个组件没有什么好解释的,它唯一的功能就是将文本展现在div中。

1
2
3
4
5
6
7
 var SpeedDisplay = React.createClass({  
   render: function() {
     return <div className="speed-display">
       { this.props.speed + " " + this.props.uom }
     </div>;
   }
 });

所有代码整合起来

KendoExample组件包含了两个变量:speed和uom。这个组件会被UnitOfMeasureKendo.Slider影响,当上述的两个组件中的任意一个产生了新的信息,KendoExample都会调用setState

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
 var KendoExample = React.createClass({
   mixins: [React.postal],
   getInitialState: function() {
     return {
       speed: 88,
       uom:"MPH"
     }
   },
   componentWillMount: function() {
     this.subscribe("change.#", function(data) {
       this.setState(data);
     });
   },
   componentWillUnmount: function() {
     this.disposeSubscriptions();
   },
   render: function() {
     return <div>
       <div className="container">
         <Kendo.RadialGauge value={this.state.speed} />
         <Kendo.Slider value={this.state.speed} id="gauge-slider" channel={this.props.channel} />
         <UnitOfMeasure channel={this.props.channel} />
       </div>
       <SpeedDisplay speed={this.state.speed} uom={this.state.uom} />
     </div>;
   }
 });

你可以在这里看到演示结果。

包装

我希望你觉得这篇文章很有用。如果你想要了解React和别的库是如何结合使用的,可以看React BootstrapWingspan Forms

更新

Christopher Chedeau(React的天才开发人员之一)评议了这篇文章并指出关于RadialGauge组件这点,他倾向使用componentWillReceiveProps来比较新旧props,然后看情况更新DOM。原先更新radial gauge是在componentDidUpdate中完成的,现在我把componentWillReceiveProps的方法更新到例子中了。React的开发团队很友善,所以不要害怕向他们提问。

关于作者: kmokidd

转载:http://blog.jobbole.com/77703/

未经允许,不得转载本站任何文章:KendoUI中文网 » 当 React 遇上 KendoUI

分享到:更多 ()

评论 1164

评论前必须登录!