React学习笔记

Nov 3, 2016


React学习笔记

来自 《React 引领未来的用户界面开发框架》一书

JSX

JavaScript XML,一种在React组建内部构建标签的类XML语法,实际会被转换成JavaScript函数。JSX的表达能力很强,也很适合组件化的React开发。

范例

//编写一个Divider
var Divider = React.createClass({
  render: function(){
    <div className="divider">
    	<h2>{this.props.children}</h2><hr/>
    </div>
  }
})

//Divider调用
<Divider>Questions</Divider>

非DOM属性

  • key 可选的唯一标志符,便于React渲染时重用
  • ref 表示父子组建之间的引用
  • dangerouslySetInnerHTML 在需要将HTML内容设置为字符串时使用

事件

事件已经被规范化,并同统一用驼峰形式表示。

handleClick: function(event){...},
render: function(){
  return <div onClick={this.handleClick}>...</div>
}

实际上,如CSS的表示也是用这样的形式。

组建生命周期

首次使用组件时

  1. getDefaultProps 可以为实例设置默认的props
  2. getInitialState 初始化每个实例初始化state
  3. componentWillMountrender之间最后修改state值的机会
  4. render 创建虚拟DOM,是组件中唯一必要的方法
  5. componentDidMount可以在该方法中通过调用this.getDOMNode()来访问真实的DOM,可以在这里调用如JQuery等工具。如果React运行在服务端,该方法不会被调用

之后使用该组件时

  1. getInitialState
  2. componentWillMount
  3. render
  4. componentDidMount

应用状态改变时

  1. componentWillReceivProps 组件的props可以通过父辈组件来修改,此时该方法将被调用
  2. shouldComponentUpdate 在渲染前判断是否需要更新,做到精确的优化,不要草率的使用该方法
  3. componentWillUpdate不可以在该方法中更新state或者props
  4. render
  5. componentDidUpdatecomponentDidMount类似

销毁时

调用componentWillUnmount,可以在这个方法中做一些清理工作,比如创建的定时器或者添加的事件监听器

数据流

React中数据流是单向的,从父节点传递到子节点,因而组件是简单且易于把握的,组件只需要从父节点获取props渲染即可。如果顶层组件的某个prop改变了,React会递归地向下遍历整棵组件树,重新渲染所有使用这个属性的组件。

Props

Props即为properties,能够把任意类型的数据传递给组件。

//挂载数据时设置组件的props
var components=[{title:'test_0'},{title:'test_1'}];
<ListComponents components={components}/>

可以通过this.props来访问组件的props,但不能通过这种方式修改它,一个组件绝对不可以自己修改自己的propsprops可以是字符串,对象,事件处理器等。可以使用PropTypes来验证props的类型,推荐使用。

State

每一个React组件都可以拥有自己的state,其与props的区别是,state只存在于组件内部

state的值应该通过this.setState()修改,只要该状态被修改,render方法就会被调用,然后导致之后的一系列变化。状态总是让组件变得更加复杂,把状态针对不同的组件独立开来,能更有利于调试。

Props和State存放数据

state中不要保存计算出的值,而应该保存最简单的数据,即为组件工作时的必要数据。而props用来作为数据源,保证数据的单向流动。

事件处理

绑定方式实际上和JavaScript并没太大差别,例如:

var Demo = React.createClass({
  render: function(){
    <div onClick={this.handleClick}>Click here</div>
  }

  handleClick: function(event){
    this.setState({isClicked : true});
  }
});

setState

合并对象的方式来更新组件的状态

replaceState

state整个替换为新的

组件的复合

React的一个突出特点是构建组件的能力,实际上React代码主要就是构建组件,就像编写HTML文档时使用元素一样。

// 封装一个radio input作为子组件
var AnswerRadioInput = React.createClass({
  	propTypes : {
     	id: React.PropTypes.string,
      	name: React.PropTypes.string.isRequired,
      	label: React.PropTypes.string.isRequired,
      	value: React.PropTypes.string.isRequired,
      	checked: React.PropTypes.bool
  	},
  	getDefaultProps : function(){
      retrun{
        id: null,
        checked:false
      }
  	},
    getInitialState : function(){
      var id = this.props.id? this.props.id : uniqueId('radio-');
      return {
        checked : this.props.checked,
        id : id,
        name : id
      }
    },
    render : function(){
      return (
        <div className="radio">
          <!-- htmlFor表示该Label为哪个控件服务,点击该label时,所比绑定的元素将获得焦点 -->
          <label htmlFor={this.props.id}>
            <input type="radio"
              name={this.props.name}
              id={this.props.id}
              value={this.props.value}
              checked={this.state.checked}/>
            	{this.props.label}
          </label>
        </div>
      )
    }
});

以上构建了一个AnswerRadioInput组件,之后构建一个AnswerMultipleChoiceQuestion来组合这一组件。

// 父组件
var AnswerMultipleChoiceQuestion = React.createClass({
	propTyes: {
	  value: React.PropTypes.string,
	  choices: React.PropTypes.array.isRequired,
	  onCompleted: React.PropTyes.func.isRequired
	},
  	getInitialState: function(){
      return {
        id: uniqueId('multiple-choice-'),
        value: this.props.value
      }
  	},
  	renderChoices: function(){
      return this.props.choices.map(function(choice,i){
        return AnswerRadioInput({
          id: "choice-"+i,
          name: this.state.id,
          label: choice,
          value: choice,
          checked: this.state.value === choice
        });
      }.bind(this))
  	},
  	render: function(){
      return (
        <div className="form-group">
        	<label className="survey-item-label" htmlFor={this.state.id}>
              	{this.props.label}
          	</label>
          	<div className="survey-item-content">
          		{this.renderChoices()}
          	</div>
        </div>
      );
  	}
});

此时存在一个问题,子组件没办法把自身的变化通知给父组件,因此,父组件需要关联子组件才能知道其更新。方法实际上就是回调,父组件通过向子组件中传入一个回调函数,子组件在需要时调用。

//父组件传入回调函数
var AnswerMultipleChoiceQuestion = React.createClass({
  ...
  handleChanged: function(value){
    this.setState({value:value});
    this.props.onCompleted(value);
  },
  renderChoices: function(){
    return this.props.choices.map(function(choice, i){
      return AnswerRadioInput({
        ...
        onChanged: this.handleChanged
      });
    }.bind(this));
  },
});

//子组件调用回调
var AnswerRadioInpu = React.createClass({
  propTypes: {
    ...
    onChanged: React.PropTypes.func.isRequired
  },
  handleChanged: function(event){
    //event.target 找到事件的触发对象input
    var checked = event.target.checked;
    this.setState({checked: checked});
    if(checked){
      this.props.onChange(this.props.value);
    }
  },
  render: function(){
    return(
      <div className="radio">
        <label htmlFor={this.state.id}>
        	<input type="radio"
              ...
              onChange={this.handleChanged}/>
          	{this.props.label}
        </label>
      </div>
    )
  }
})

Mixin

mixin允许我们定义可以在多个组件中共用的方法

// 定义IntervalMixin
var IntervalMixin = {
	setInterval: function(callback, interval){
		var token = setInterval(callback, interval);
		this.__intervals.push(token);
		return token;
	},
	componentDidMount: function(){
		this.__intervals=[];
	},
	componentWillUnmount: function(){
		this.__intervals.map(clearInterval);
	}
}

//定义组件
var Since2014 = React.createClass({
	//实际相当于把mixins中的内容解构了出来,放在了Since2014中
	mixins: [IntervalMixin],
	componentDidMount: function(){
		this.setInterval(this.forceUpdate.bind(this),1000);
	},
	render: function()	{
		var from = Number(new Date(2014, 0, 1));
		var to =Date.now();
		return (
			<div>{Math.round((to-from)/1000)}</div>
​		);
​	}
});

DOM操作

在某些情况下,为了实现某些需求就不得不去操作底层的DOM。最常见的场景包括:需要与一个没有使用React的第三方类库进行整合,或者执行一个React没有原生支持的操作。

var DoodleArea = React.createClass({
	render: function(){
		//通过设定一个ref属性,之后可以通过this.refs.mainCanvas来访问到<canvas>组件
		return <canvas ref="mainCanvas"/>;
	}
	componentDisMount: function(){
		//在这里通过getDOMNode()方法来访问到底层的DOM节点,也可以在事件处理器中调用
		var canvasNode= this.refs.mainCanvas.getDOMNode();
	}
});

表单

无约束组件

组件本身控制其值

var MyForm = React.createClass({
	submitHandler: function(event){
		event.preventDefault();
		var text=this.refs.text.getDOMNode().value;
		alert(helloTo);
	},
	render: function(){
		return (
			<form onSubmit={this.submitHandler}>
				<input ref="text" type="text" defaultValue="Hello world!"/>
				<br/>
				<button type="submit">Submit</button>
​			</form>
​		);
	}
});

约束组件

约束组件的值被存储在state中,由React控制

var MyForm = React.createClass({
	getInitialState: function(){
		return {
			text: 'hello world!'
		}
	},
	
	render: function(){
		return (
			<form onSubmit={this.submitHandler}>
				<input type="text" value={this.state.text}
					 onChange={this.handleChange}/>
​				...
			</form>
​		);
	}
});

对于如handleChange 方法,还可以用bind方法传递更多参数,例如this.handleChange.bind(this, "name")

React Router

React的介绍即讲过其非常适合于单页面应用,页面之间的切换需要Router的帮助。

React-router完全由React Component构成,路由被定义为了组件,路由的处理器也是组件

//react router的写法如下
var appRouter=(
	<Routes location="history">
		<Route title="SurveryBuilder" handler={App}>
			<Route name="list" path="/" handler={ListSurveys}/>
​			<Route title="Add survey to surveyBuilder" name="add" 
				path="/add_survey" handler={AddSurvey}/>
​			<NotFound title="Page Not Found" handler={NotFoundHandler}/>
​		</Route>
​	</Routes>

);

每一个处理器就对应着特定页面的组件,需要将上面的路由作为最顶层的组件渲染来启动。

使用react-router Link组件来进行导航。


上一篇博客:VPN搭建小记
下一篇博客:读书笔记-思