一、在getInitialState里边使用 props 是一种反面模式
提示:这不是React明确规定的,就像经常发生在其它代码中的那样;在这种情况下,React能使这种模式更清晰。
在getInitialState
中使用props传递的父组件的数据来绑定state,经常会导致所谓的真理的重复来源(就是说可能分不清数据来源了)。只要有可能动态推断值,以确定不会发生不同步以及维护困难的情况。(小僧翻译出来也快吐了,原文如果不引入俚语还好,一旦用了就晕乎了,不过在最下方小僧会大致总结下,客官勿急)。
反面示例:
var MessageBox = React.createClass({ getInitialState: function() { return {nameWithQualifier: 'Mr. ' + this.props.name}; },render: function() { return <div>{this.state.nameWithQualifier}</div>; } }); React.render(<MessageBox name="Rogers"/>,mountNode);好的示例:
var MessageBox = React.createClass({ render: function() { return <div>{'Mr. ' + this.props.name}</div>; } }); React.render(<MessageBox name="Rogers"/>,mountNode);
然而,如果你明确表示同步不是你的目标,那么也可以写成非反向模式。
var Counter = React.createClass({ getInitialState: function() { // naming it initialX clearly indicates that the only purpose // of the passed down prop is to initialize something internally return {count: this.props.initialCount}; },handleClick: function() { this.setState({count: this.state.count + 1}); },render: function() { return <div onClick={this.handleClick}>{this.state.count}</div>; } }); React.render(<Counter initialCount={7}/>,mountNode);
个人总结:getInitialState中初始化的数据,原则是保持在该组件及其子组件使用,不要将父组件的数据(props传递过来的数据)使用在这里。如果你想在该组件内使用的数据需要根据props传递过来的数据来初始化,那就是你的数据原型有问题了,为什么不在其它方法中直接使用父组件的数据。与父组件通信,最好是通过回调函数,该回调函数是从父组件传递过来,并且里边封装了state绑定的数据(即更改的数据)。
二、在组件中使用DOM事件
注意:这里说的事件是非React提供的事件。React有关于DOM的操作可以很容易和其它类库合作,如jQuery。
试着改变窗口大小:
var Box = React.createClass({ getInitialState: function() { return {windowWidth: window.innerWidth}; },handleResize: function(e) { this.setState({windowWidth: window.innerWidth}); },componentDidMount: function() { window.addEventListener('resize',this.handleResize); },componentWillUnmount: function() { window.removeEventListener('resize',render: function() { return <div>Current window width: {this.state.windowWidth}</div>; } }); React.render(<Box />,mountNode);
componentDidMount
会在组件被渲染之后被调用。在这个方法中通常被用来给DOM添加事件,因为只有DOM(渲染成功)存在,才能给其添加事件。
注意这里的事件是绑定到了React中的组件,而不是最原始的DOM。React会通过一个叫 autobinding 的过程将这个事件方法绑定到组件中。
三、用ajax加载初始数据
在componentDidMount
中获取数据:当响应到达时,会将数据存储的state中,然后触发更新UI。
当处理异步请求的响应时,要确定当前组件是否仍然在更新中,通过 this.isMounted() 来判断。
var UserGist = React.createClass({ getInitialState: function() { return { username: '',lastGistUrl: '' }; },componentDidMount: function() { $.get(this.props.source,function(result) { var lastGist = result[0]; if (this.isMounted()) { this.setState({ username: lastGist.owner.login,lastGistUrl: lastGist.html_url }); } }.bind(this)); },<pre name="code" class="javascript">React.render(<div id={false} />,mountNode);
render: function() { return ( <div> {this.state.username}'s last gist is <a href={this.state.lastGistUrl}>here</a>. </div> ); }});React.render( <UserGist source="https://api.github.com/users/octocat/gists" />,mountNode); 注意:例子中的bind(this),只能用在函数
四、在JSX中使用false
下边是不同情况系 false 渲染的过程:
1. 渲染成id=“false”:
React.render(<div id={false} />, mountNode);2. 渲染成输入框中的字符串值
React.render(<input value={false} />,mountNode);3. 没有子元素的情况
React.render(<div>{false}</div>,mountNode);在这种情况中,false被渲染成<div></div>之间的文本,而是作为一个div的子元素,是为了可以有更多灵活的用法:如
<div>{x > 1 && 'You have more than one item'}</div>
五、组件间的通信
在父子组件之间的通信,可以简单的通过props。
而在同级子组件之间的通信:假如你的 GroceryList
组件有很多通过数组生成的条目。当其中一个条目被点击时,你想要展示该条目的名称。
var GroceryList = React.createClass({ handleClick: function(i) { console.log('You clicked: ' + this.props.items[i]); },render: function() { return ( <div> {this.props.items.map(function(item,i) { return ( <div onClick={this.handleClick.bind(this,i)} key={i}>{item}</div> ); },this)} </div> ); } }); React.render( <GroceryList items={['Apple','Banana','Cranberry']} />,mountNode );注意bind(
this,arg1,arg2,...
)的用法:这里我们只是去传递更多的参数给
handleClick
方法。这并不是React新加的规定,而是JavaScript就有的。
在两个同级子组件之间的通信,你可以设置一个全局的事件componentWillUnmount
。在 componentDidMount 中注册监听,在
componentWillUnmount
卸载监听,当监听到一个事件时,调用setState()更新视图。
六、暴露组件的功能
组件之间的通信也有一个不常见的方法:可以简单的在子组件中调用父组件的回调函数,此处的子组件拆分更加琐碎。
下边的例子中,每点击一个条目然后自身被删除,当仅剩一个条目时,就为其加上动画效果。
var Todo = React.createClass({ render: function() { return <div onClick={this.props.onClick}>{this.props.title}</div>; },//this component will be accessed by the parent through the `ref` attribute animate: function() { console.log('Pretend %s is animating',this.props.title); } }); var Todos = React.createClass({ getInitialState: function() { return {items: ['Apple','Cranberry']}; },handleClick: function(index) { var items = this.state.items.filter(function(item,i) { return index !== i; }); this.setState({items: items},function() { if (items.length === 1) { this.refs.item0.animate(); } }.bind(this)); },render: function() { return ( <div> {this.state.items.map(function(item,i) { var boundClick = this.handleClick.bind(this,i); return ( <Todo onClick={boundClick} key={i} title={item} ref={'item' + i} /> ); },this)} </div> ); } }); React.render(<Todos />,mountNode);
或者,你也可以通过在子组件 todo 中定义一个 isLastUnfinishedItem 属性,然后在
componentDidUpdate
进行判断,是否添加动画效果。然而,当有多个属性来控制动画时就显得混乱了。
七、组件引用
当你在一个大的非React应用中想要使用React的组件或者将你的代码过渡到React,你需要想办法引用组件。React.render
方法返回的就是一个完整组件的引用。
var myComponent = React.render(<MyComponent />,myContainer);
要记住,JSX语法并不会返回一个组件实例。它只是一个React元素:一个组件的轻量级表现方式。
var myComponentElement = <MyComponent />; // This is just a ReactElement. // Some code here... var myComponentInstance = React.render(myComponentElement,myContainer);注意:这里只能在顶级作用域(组件外部)中使用。在组件内部应该使用 props 和 state 来保持组件间的通信。
八、this.props.children 未定义
你不能使用this.props.children来访问组件的子节点。this.props.children被设计用来表示自身的节点。
var App = React.createClass({ componentDidMount: function() { // This doesn't refer to the `span`s! It refers to the children between // last line's `<App></App>`,which are undefined. console.log(this.props.children); },render: function() { return <div><span/><span/></div>; } }); React.render(<App></App>,mountNode);想要访问子节点,就要使用在span中ref