官网地址https://facebook.github.io/react/docs/hello-world.html
JSX
- 可以在JSX中插入任何表达式,表达式需要用
{}
包裹,插入string,可以用""
- JSX分割成多行时,用
()
包裹起来,避免自动分号插入的陷阱 - JSX的属性采用驼峰式命名方式,如HTML中的
class
,JSX命名为className
- JSX避免XSS (cross-site-scripting) 攻击
- Babel通过调用
React.createElement()
来编译JSX,React必须在JSX代码的作用域内,参考:深入JSX -
React.createElement()
创建的对象成为"React elements"
Rendering elements
- 元素是React应用的最小组成单位
- React的元素都是对象,创建廉价
- 通过
ReactDOM.render()
将React元素渲染成DOM元素,如ReactDOM.render(element, document.getElementById('root'));
- React elements是不可变( immutable)的,它代表某个时刻的UI改变UI的唯一方法就是创建一个新的element,然后把它传递给
ReactDOM.render()
- React DOM会将新创建的element以及它的children和前一个element相比,只改变DOM中需要改变的部分来更新DOM
Components and Props
- Components将UI分割成独立的、可复用的、可单独思考的块
- 可以使用函数或者类定义类,分别称为Function Component(函数组件)和Class Component(类组件)
Function Component:函数的入参只能是一个props对象,并且返回值是一个React元素
Class Component:class继承React.Component,有一个render属性,render返回一个自定义组件、DOM组件或者null和false(null和false表明不需要渲染任何东西) - 组件名的首个字母必须大写,如<Welcome />,用于区分自定义元素(首个字母大写)和DOM元素(首个字母小写)
- Component返回元素必须是一个根元素
- 纯函数:不改变输入值的函数,所有的组件必须是像纯函数一样,不改变props,props是只读的
- props默认值是"true"
- 可以使用……作为扩展操作来传递全部props对象,
<Greeting {...props} />
- 在开口标签和闭合标签之间的内容是一种特殊的props:
props.children
-
props.children
可以是混合的JSX、{}
包裹的表达式或者函数 - false, null, undefined, and true都是有效孩子,不会被渲染,需要渲染时,使用{String(myVariable)}转化为string
6-10参考:深入JSX
State and Lifecycle
- 只有通过class方法定义的组件才有state属性
- 在class的constructor中,通过
super(props)
继承props,通过this.state = {……}
来初始化state,只能通过setState来修改state - state的修改是异步的,setState的callback函数可以传入两个参数:prevState, props(前一个状态、参数)
this.setState((prevState, props) => {……})
- 会对setState返回的对象与state做一个merge操作
- 生命周期钩子
componentDidMount()
:组件插入DOM(render()
完成)时立刻执行
componentWillUnmount()
:在组件即将从 DOM 中移除的时候立刻被调用 - 数据是从父组件到子组件单向流动的,可以将state作为props传递给子组件
Handling Events
- 命名事件方式不同:react->驼峰式(如
onClick
),DOM->小写(如onclick
) - react传递函数(如:
onClick={activateLasers}
)给事件处理器而不是string(如:onclick="activateLasers()"
) - DOM调用return false来阻止默认行为,在react中,必须显示调用
preventDefault()
- 注意JSX事件回调函数中的
this
,可以在constructor
函数中绑定this.handleClick = this.handleClick.bind(this);
或者handleClick = () => {……}
或者button onClick={(e) => this.handleClick(e)}>
,这样可以保证函数在实际使用时this
不时undefined
Conditional Rendering
- 使用js的if 或者条件运算符 创建符合当前状态的元素
- 在JSX中插入表达式,&&操作符或者条件表达式编写内联的if逻辑
- 根据条件隐藏(返回null)或者显示组件
- 返回null,
componentWillUpdate()
和componentDidUpdate()
仍然会调用
componentWillUpdate(object nextProps, object nextState)
:在接收到新的 props 或者 state 之前立刻调用
componentDidUpdate(object prevProps, object prevState)
:在组件的更新已经同步到 DOM 中之后立刻被调用
Lists and Keys
- 创建elements列表时,key是一个特殊的属性
- react根据keys判断数组的items(项)是否改增删改
- 在兄弟节点中,该节点的key是独一无二的
- key应该是稳定的(例如不能是通过Math.random()生成)
- key是可预测的
- 添加一个ID属性或者对部分内容做哈希算法生成一个key,也可以将item在数组中的index作为key,确保index作为key时,这个数组不会重新排序,重新排序可能会很慢
- 在JSX中插入
map()
,生成子组件列表
<pre>return ( <ul> { numbers.map((item, index) => { return <ListItem value={item} key={index} /> }) } </ul> )
</pre>
Forms
- controlled component:一个input表单元素,如果它的value是由React控制的,我们就叫它controlled component
- form表单,如
<input type="text" value={this.state.value} onChange={this.handleChange} />
,handleChange
事件通过setState
修改state
的值,最后修改表单的值。表单的值由React控制
Lifting State Up
多个不同的component之间可能需要共享一个state,可将state提升至它们共同的最近的component祖先中(从上至下的数据流)
- state提升需要写一个共同的模板,可能需要写更多的代码
- 如果有些数据,既可以放在props又可以放在state中,那么建议不要放在state中
Composition vs Inheritance
react有强大的组合模型,在react建议使用组合来代替继承。新手在开发过程中经常遇到的,用组合代替继承的案例有:
- 组件的孩子节点事先不知道,通过
props.children
传入 - 组件预留一些“洞”(构成组件的部分模块开始未知,在使用时才知道),可以在使用时通过props(props的属性可以是component)传入这些模块(模块可能是组件)
- 一个组件是另外一个组件的特例时
JSX In Depth
- JSX是
React.createElement(component, props, ...children)
的语法糖 - 可以使用点记法标识一个JSX类型,如
<MyComponents.DatePicker color="blue" />
- React元素不能是普通表达式,如果想使用普通表达式,可以先赋值给一个大写的变量,
SpecificStory = components[props.storyType]; return <SpecificStory />
- React元素和自定义组件必须大写
Refs and the DOM
React提供了Refs(引用),用ref来获取组件或者HTML元素的引用,ref有一个属性是一个回调函数,回调函数入参是ref所在的HTML元素或者组件的引用,如<input type="text" ref={input => this.textInput = input} />
- refs使用场景:处理焦点、文本选择、媒体播放,触发强制性动画, 集成第三方DOM库
- 可以给DOM和类组件refs,函数组件没有实例,不适合使用ref属性
- 不要过度使用Refs
Uncontrolled Components
- 使用uncontrolled components,可以更容易集成React和非React代码,代码可能会更加轻量,但是会比较“脏”,尽量使用controlled components
- 在为form中每一个state变化写一个事件处理器不如使用ref获取DOM表单的值,对表单中的每一个元素,通过
ref={(input) => this.input = input}
来获取引用 - defaultValue指定初始值,在checkbox和radio中,使用defaultChecked,select使用defaultValue ,如
<input defaultValue="Bob" …… />
Optimizing Performance
使用构建工具优化React应用
使用chrome的Timeline剖析组件
virtual DOM:对被渲染成了UI的组件,React会构建和保存该组件返回的React元素,即virtual DOM
shouldComponentUpdate:生命周期函数
shouldComponentUpdate(nextProps, nextState)
默认返回true,如果返回一个false,就不会重新渲染组件-
组件渲染时,从上而下遍历该组件树
a:如果节点N1的shouldComponentUpdate
方法返回false,N1及其子节点Ni(i可能是1、2、……)都不会重新渲染,Ni的shouldComponentUpdate
也不会触发,停止遍历,否则执行b
b:依次遍历子节点Ni,如果Ni不是叶子节点,对Ni执行a操作,否则执行c
c:只有在Ni(叶子节点)的shouldComponentUpdate
返回true并且React比较Ni改变前后的值不相等时候,才会重新渲染 ** React.PureComponent**:可使用
React.PureComponent
来代替shouldComponentUpdate
,React.PureComponent会做一个“浅比较”,所以对于props或者state修改前后,浅比较值依旧相等的情况,不要使用React.PureComponent修改数组和对象,“浅比较”修改前后值依旧相等
对数组:可使用concat
和es6扩展语法,重新赋值如words: prevState.words.concat(['marklar'])
和words: [...prevState.words, 'marklar']
对对象:可使用object.assign或者对象扩展属性重新赋值Object.assign({}, colormap, {right: 'blue'})
和{...colormap, right: 'blue'}
Immutable.js是另一个解决方案,它通过结构化共享提供一个不变化、持久化的集合
Reconciliation
React使用Diffing Algorithm(差分算法)来考虑如何有效的修改UI来最大程度的匹配现在的树
- Diffing Algorithm的启发式设想前提
a:两个不同的元素会生成两个不同的树
b:开发者可以通过key暗示在不同的渲染中,哪个孩子元素是稳定不变的 - Diffing算法
a:对于不同类型的根元素,React会销毁旧的树以及它的state,并且安装一颗新的树
b:相同类型的DOM元素,保留相同的DOM元素,只更新已更改的部分属性(例如class、style属性等)
c:相同类型的Component元素,新旧Component是同一个对象实例,维持一个state,React更新组件的props来匹配新的元素(componentWillReceiveProps()和componentWillUpdate()事件会执行)
d:React会同时递归比较新旧孩子列表,一旦有不同的地方,React就会改变
上述递归新旧孩子列表,如果节点只是挪动了位置,并没有改变,React不会意思到这一点,会造成不必要的销毁与重建,React可以使用key来比较修改前后的孩子节点。 - Tradeoffs
a:这个算法不会试着匹配不同组件类型的子树,有相似输出的组件类型的组件之间可能更应该是同一个类型,事实也是这样
b:key应该是稳定的,可预测的,和独一无二的 - 生命周期事件
componentWillUnmount()
:在旧的DOM节点即将销毁时调用
componentWillMount()
:即将安装新的DOM时调用
componentDidMount()
:新的DOM安装完成是调用
componentWillReceiveProps()
:组件即将更新props时调用
componentWillUpdate()
:组件接收到新的props或者state但还没有render()时被执行