最近在学习React,并使用React做了一个cnode(欢迎大家给我star、issue,一起学习讨论进步),现就记录一下自己的React学习笔记。
学习资料
环境配置
React的环境配置很麻烦,刚上手时可以使用React脚手架来进行学习。推荐Create React App,或者可以使用我自己写的一个脚手架。
Jsx语法
简单来说,就是可以把html和js混在一起写,即html代码里面可以有js代码,js代码里面可以有html。需要注意的是,在html中遇到js代码,需要加花括号,而在js代码中遇到html代码,需要加圆括号。
创建组件
创建组件有两种方式:
- 函数创建
function Hello() {
return <h1>Hello World</h1>;
}
- ES6的class
class Hello extends React.Component {
render() {
return <h1>Hello World</h1>;
}
}
不同之处:函数式组件没有state,也不能使用生命周期函数。
注意:组件名称必须以大写字母开头。组件的返回值只能有一个根元素。
props与state
- props
props是只读的,组件绝对不能修改自己的props - state
状态与属性十分相似,但是状态是私有的,完全受控于当前组件。
更新状态只有一个办法:那就是调用this.setState()
该方法有两种调用方式// 接受一个对象为参数 this.setState({ loading: false }) // 接受一个函数为参数,函数的第一个参数为先前的状态,第二个参数为props this.setState((prevState, props) => ({ loading: !prevState.loading })) // 除此之外,this.setState()还接受一个可选的回调函数作为第二个参数
条件加载
React的条件加载和JavaScript中的条件判断一样,我们可以使用条件运算符(?:),与运算符(&&)等来进行条件加载。
循环加载
我们一般按如下的方式进行循环加载
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
)
//一个元素的key最好是这个元素在列表中拥有的一个独一无二的字符串。
//通常,我们使用来自数据的id作为元素的key。当元素没有确定的id时,你可以使用他的序列号索引index作为key
操作表单
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
render() {
return (
<div>
<label>Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
</div>
);
}
}
一个简单操作表单元素的例子就是这样。但是我们会发现这样操作表单有一些小问题。假如该表单有很多表单元素,那么我们需要为每一个表单元素注册一个change事件的处理函数,这会让组件显得很臃肿。不过不用担心,遇到这种情况我们依然有解决办法。那就是使用ref。
关于Ref
我们可以给DOM元素,类组件添加ref属性,不能给函数式组件添加ref属性。
ref 属性接受一个回调函数,它在组件被加载或卸载时会立即执行。
ref属性也接受一个字符串,(不过未来可能会废弃,推荐使用回调)
ref 在加载时回调接收了底层的DOM元素或已经加载的 React 实例作为参数,在卸载时则会传入 null。
class NameForm extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<label>Name:
<input type="text" ref={ input => this.input=input }/>
</label>
</div>
);
}
}
// 此时this.input指向inputDOM元素,我们可以使用this.input.value得到用户的输入。
除可以操作DOM之外,ref还用于触发强制动画,处理焦点、文本选择或媒体控制等。
不过我们应该尽量避免使用ref。
事件处理
- React事件绑定属性的命名采用驼峰式写法。
- 如果采用 JSX 的语法需要传入一个函数作为事件处理函数,而不是一个字符串。
- 不能使用返回 false 的方式阻止默认行为。
- 事件对象是一个合成对象,所以不需要担心跨浏览器的兼容性问题。
- 事件处理程序会成为类的一个方法(类的方法默认是不会绑定this 的),所以我们需要手动绑定this。
手动绑定this有两种方法,一是在类的构造函数中使用bind绑定,二是在DOM中使用bind绑定。
如果你的事件处理程序是箭头函数,则不需要手动绑定this。 - 向事件处理程序传递参数有两种方法,一是使用箭头函数,二是使用bind方法。
<button onClick={(e) => this.confirm(id, e)}>确定</button>
<button onClick={this.confirm.bind(this, id)}>确定</button>
- 事件处理程序的最后一个参数为事件对象(该事件对象是SyntheticEvent的实例)。
如果由于某些原因,你得使用一些底层的浏览器事件,只需用nativeEvent的属性就能得到原生的事件对象。
DOM属性
React实现了一套与浏览器无关的DOM系统,兼顾了性能和跨浏览器的兼容性。
在React中,所有的DOM特性和属性都是小驼峰命名法命名。(aria-和data-属性除外)
一些不同之处:
- 使用className属性指定一个CSS类。
- style属性接受一个键为小驼峰命名法命名的javascript对象作为值。(注意:样式属性不会自动补齐前缀的,浏览器前缀除了ms以外,都应该以大写字母开头)
- onChange函数,无论form表单何时发生变化,这个事件都会被触发。
- dangerouslySetInnerHTML函数是替换浏览器DOM中的innerHTML接口的一个函数。
生命周期
组件的生命周期大致分为三个阶段
-
装配阶段(这些方法会在组件实例被创建和插入DOM中时被调用)
constructor()
componentWillMount()
render()
componentDidMount()
-
更新阶段(属性或状态的改变会触发一次更新。当一个组件在被重渲时,这些方法将会被调用)
componentWillReceiveProps(nextProps)
shouldComponentUpdate(nextProps, nextState)
componentWillUpdate(nextProps, nextState)
render()
componentDidUpdate(prevProps, prevState)
-
卸载阶段(当一个组件被从DOM中移除时,该方法被调用)
componentWillUnmount()
除此之外,组件还有一个方法forceUpdate()
,调用forceUpdate()
将会导致组件的 render()
方法被调用,并忽略shouldComponentUpdate()
。
注意点:
- 若
shouldComponentUpdate()
返回false,render()
函数将不会被调用。 - 当为一个React.Component子类定义构造函数时,你应该在任何其他的表达式之前调用
super(props)
。否则,this.props
在构造函数中将是未定义,并可能引发异常。 -
defaultProps
可以被定义为组件类的一个属性,用以为类设置默认的属性。
常见问题
- 使用React开发必须使用JSX语法吗?
答:JSX并不是必须的。每一个JSX元素都只是React.createElement(component, props, ...children)
的语法糖。
因此,任何时候你用JSX语法写的代码也可以用普通的 JavaScript 语法写出来。 - 使用React开发必须使用ES6+语法吗?
答:ES6+并不是必须的。
虚拟DOM
- 虚拟DOM具有batching(批处理)和高效的Diff算法。
- batching把所有的DOM操作搜集起来,一次性提交给真实的DOM。diff算法时间复杂度也从标准的的Diff算法的O(n^3)降到了O(n)。
- render执行的结果得到的并不是真正的DOM节点,结果仅仅是轻量级的JavaScript对象,我们称之为virtual DOM。
- 我们利用虚拟DOM树去构造真实DOM树,然后插入到文档中,当数据变化时,生成一个新的虚拟DOM树,比较新的虚拟DOM树与旧的虚拟DOM树,得到差异,将差异应用到真实DOM中。
- Virtual DOM并没有完全实现DOM,Virtual DOM最主要的还是保留了Element之间的层次关系和一些基本属性。
React diff算法
- 树对比
React 对树的算法进行了简洁明了的优化,即对树进行分层比较,两棵树只会对同一层次的节点进行比较。 - 组件对比
- 如果是同一类型的组件,按照原策略继续比较 virtual DOM tree。
- 如果不是,直接删除旧组件,然后在该位置创建新组件。
- 对于同一类型的组件,有可能其 Virtual DOM 没有任何变化,如果能够确切的知道这点那可以节省大量的 diff 运算时间,因此 React 允许用户通过 shouldComponentUpdate() 来判断该组件是否需要进行 diff。
- 元素对比
允许开发者对同一层级的同组子节点,添加唯一 key 进行区分,虽然只是小小的改动,性能上却发生了翻天覆地的变化! - 总结
- React 通过分层求异的策略,对 tree diff 进行算法优化;
- React 通过相同类生成相似树形结构,不同类生成不同树形结构的策略,对 component diff 进行算法优化;
- React 通过设置唯一 key的策略,对 element diff 进行算法优化;
性能优化
- 一般做法
- 使用工具来分析性能瓶颈。
- 尝试使用优化技巧解决这些问题。
- 使用工具测试性能是否确实有提升。
- 具体实施
很大程度上,React的性能优化就是干掉无谓的渲染。- 我们可以重写
shouldComponentUpdate()
方法,对this.props与nextProps进行比较,对this.state与nextState进行比较,如果有改变,则返回true,否则返回false。 - 组件继承
React.PureComponent
。(自动帮我们进行浅比较) - 使用
pure-render-decorator
装饰器。(自动帮我们进行浅比较) - 使用
react-addons-shallow-compare
。(自动帮我们进行浅比较) - 使用immutable.js。
- 使用seamless-immutable。
- 我们可以重写
- 性能分析工具
- React.addons.Perf
- react-perf-tool
参考
使用immutable优化React
React性能优化总结
React 源码剖析系列 - 不可思议的 react diff
未完待续....