一、JSX
1 JSX定义及优点
- JavaScript XML
- 每个JSX节点都对应一个js函数
- JSX好处:语义化,抽象化(React版本更新时代码改动得少),关注点分离(逻辑+标签封装在组件中)
2 使用组件
- 使用动态值:
{}
中的任何东西都会被求值:变量,函数求值,字符串数组自动求值为拼接字符串。 - 子节点:
{this.props.children}
,React会将放在该组件标签之间的任何东西渲染出来。举例:定义组件Divider
123456789var Divider = React.createClass({render: function(){return (<div className="divider clearfix"><h2>{this.props.children}</h2><hr /></div>);}});
使用组件:<Divider>Questions</Divider>
这里的Questions
就是this.props.children
渲染输出的结果为:
3 JSX不同于HTML的地方:
- 用花括号设置组件的内联属性
- 条件判断:不要在花括号中写
if-else
,应使用:
(1) 三目运算符12345render: function(){return <div className={this.state.isComplete ? 'is-complete' : ''}>...</div>}
(2)设置一个变量并在属性中引用它
(3)将逻辑转化到函数中
(4)使用&&
运算符
- 特殊属性key和ref:
- key是组件的唯一标识符,React根据它智能地决定应该重用还是销毁并重新创建某个组件。当两个已经渲染于DOM中的组件交换位置时,React能匹配对应的key而不需要重新渲染DOM。提升了渲染性能。
- 父组件在
render
方法之外保持对子组件的引用。可以在组件中的任何位置使用this.refs.refName
获取这个引用(是支持实例,不是真正的DOM节点),通过this.refs.refName.getDOMNode()
访问真实的DOM节点。
事件:捕获事件 => 设置属性
注释:
(1) 作为子节点:用花括号,可多行1234567<div>{/*commentswith multiple lines*/}<input name="email" placeholder="Email Address"></div>
(2) 作为内联属性:单行/多行
特殊属性:为了不和javascript语法冲突
label
标签里的for
属性要用htmlFor
;class
属性要用className
样式
4 没有JSX的React
二、组件生命周期
- 生命周期API
- 是钩子函数
- 组件是个状态机:对于特定的输入总会返回一致的输出
- 组件的整个生命周期中,其
props
或state
改变了则DOM表现也有相应变化
1 实例化
当一个组件第一次实例化时依次调用:
(1) getDefaultProps
- 一个组件类只会调用一次。
- 没有被父组件指定
props
属性的新建实例会通过这个方法返回的对象设置默认的props
值。 - 对象和数组,会在所有实例中共享,而不是拷贝。
(2) getInitialState
- 组件类的每个实例有且只有一次调用
- 在这个方法里可以访问到
this.props
(3) componentWillMount
- 在首次渲染完成前被调用
render
前最后一次修改组件state
的机会
(4) render
- 创建一个虚拟DOM,表示组件的输出
- 组件唯一一个必需的方法
- 只能通过
this.props
和this.state
访问数据 - 可以返回
null
,false
或任何React组件 - 只能出现一个顶级组件(所以经常用
<div>
包裹返回的一组元素) - 不能改变组件的状态,不能修改DOM的输出 => “纯净”
(5) componentDidMount
真实的DOM被渲染后,可在该方法内部通过this.getDOMNode()
访问到真实DOM
注意:该组件类后续所有应用将不会调用getDefaultProps
方法,而是依次调用上面第2到5个方法。
2 存在期
组件已经渲染完成,用户可以与它交互(通过鼠标键盘等触发事件处理器)。用户会改变组件或整个应用的state
,新的state
会流入组件树。依次调用:
(1) componentWillReceiveProps
组件的props
随时可以通过父辈组件来更改。在该方法中可以更改props
对象和更新state
(使用setState()
)。
(2) shouldComponentUpdate
大部分时间没必要使用,准确测量出性能瓶颈后用于性能调优:
- 如果组件不需要渲染新的
props
或state
,该方法返回false
: React会跳过render
前后的钩子函数componentWillUpdate
和componentDidUpdate
- 会调用该方法的React插件:
PureRenderMixin
,用于组件及其子组件均为“纯净的”(对于相同的props
和state
,总是渲染出相同的DOM)。它比较当前和接下来的props
和state
,相同则返回false
。但只是浅比较对象,仅用于props
和state
为简单数据结构时。如果包含复杂数据结构,应使用forceUpdate()
(3) componentWillUpdate
和componentWillMount
类似。组件接收到新的props
或state
,渲染之前调用。该方法中不可以更新props
或state
(4) render
(5) componentDidUpdate
和componentDidMount
类似。可以更新已经渲染好的DOM
3 销毁&清理期
当组件被使用完成后,调用componentWillUnmount
方法清理该实例。在componentDidMount
中添加的定时器、事件监听器等,需要在该方法中撤销。
4 不能把计算后的值赋给state
在getInitialState
中不能通过this.props
来创建state
。如果要通过props
计算值赋给state
,则应该放在render
中执行。这样才能保证计算后的值不会与派生出它的props
值不同步。
React优势:专注于维护数据的单一来源。
三、数据流
React单向数据流:父节点 -> 子节点
组件只需从父节点获取props
渲染即可。顶层组件的props
如果改变,React会递归向下遍历整棵组件树,重新渲染所有使用这个属性的组件。
React组件 => 函数:参数为props
和state
,返回结果为一个虚拟DOM
props
可以为任意数据结构。
设置属性的方法:挂载到组件上;调用组件实例的setProps
方法(很少用)。
不能通过setProps
或this.props
修改!组件不能自己修改自己的props
props
可以用来添加事件处理器:
PropTypes
组件的配置对象,验证props
state
state
与props
的区别:前者只存在于组件的内部state
可用于确定元素的视图状态state
不能用this.state
直接修改,只能通过this.setState
修改,后者一旦调用,render
就会被调用。如果render
返回值有变化,虚拟DOM就更新,真实的DOM也会更新。
state和props中的各是哪些数据
- 不要在
state
中保存计算出的值,只保存最简单的数据,即组件正常工作时的必要数据,如:复选框的勾选状态,输入框的值,下拉选框是否显示的布尔值等。 - 不要把
props
复制到state
中,尽可能把props
当数据源。
四、事件处理
- 通过内联属性绑定事件处理器
- 在事件处理器中更新组件的状态,触发组件重绘。
- 更新状态:使用
this.setState(obj)
仅仅影响某一个或某几个状态属性,即:将obj
与原stateObj
合并,this.replaceState(obj)
是将新的obj
代替原来的stateObj
。不要使用这两种方式以外的方式修改state
- 在
render
函数中可以读取this.state
,根据状态进行渲染 - 事件对象
event
:和原生js一样。比如通过event.target.value
获取input
表单的值。React将原生事件封装在SyntheticEvent
实例当中,而不是将原生的浏览器事件对象传给事件处理器。但SyntheticEvent
在表现和功能上同浏览器的原生事件对象一致,且消除了某些跨浏览器差异。通过SyntheticEvent.nativeEvent
访问浏览器原生事件
五、组件的复合
父、子组件的通信:
- 使用属性:父组件通过属性传入一个回调函数,子组件在需要时进行调用1234567891011121314151617181920212223242526272829303132333435363738394041//父组件var AnswerMultipleChoiceQuestion = React.createClass({...handleChanged: function(value){this.setState({value: value});},renderChoices: function(){return this.props.choices.map(function(choice, i){return AnswerRadioInput({ //子组件...onChanged: this.handleChanged //将父组件的函数挂在到子组件的属性中});}.bind(this)); //为了在子组件中能访问到父组件的函数},...});//子组件var AnswerRadioInput = React.createClass({propTypes: {...onChanged: React.PropTypes.func.isRequired},handleChanged: function(e){var checked = e.target.checked;this.setState({checked: checked});if(checked){this.props.onChanged(this.props.value);//子组件在这里调用了父组件的函数}},render: function(){return (<label htmlFor={this.state.id}><input type="radio"...onChanged={this.handleChanged}/>//子组件将父组件的函数挂载起来,以便通过this.props.onChanged调用{this.props.label}</label>);}});
六、DOM操作
需要操作底层DOM的场景:
- 首先要通过
this.refs.refName
访问到负责控制这些DOM的组件(该组件要添加ref
属性,且值唯一) - 访问到组件后,通过
this.resf.refName.getDOMNode()
访问真正的DOM节点。但是只能在组件挂载之后访问,即:该方法的执行环境不能是render
,可以是componentDidMount
和事件处理器函数。