JSX
是js的一种语法扩展,在React中用于描述UI。
- 在JSX中使用
{}
嵌入任何js表达式 - 多行JSX最好使用括号wrap
- 编译后,JSX表达式成为常规的JavaScript对象。因此可以在if或者for循环中使用,或者赋值给其他的变量、从函数中返回等等。
- 由于JSX比起HTML更接近JavaScript,所以React DOM使用驼峰命名法为属性命名而不是使用HTML属性名称。
比如,class在JSX中变为className。 JSX可以阻止注入攻击。
在jSX中包含用户输入是安全的,因为默认情况下ReactDOM在渲染JSX之前会将JSX中嵌入的值进行转义,有助于防御XSS攻击。Babel将JSX编译成对
React.createElement()
的调用,最终会生成一个描述元素属性的object。这个object就是React元素,React会读取这些object并最终构建为DOM。
渲染
与DOM元素不同,React元素就是纯Object,因此创建它的代价是很低的。
React DOM负责根据React元素来更新DOM。
ReactDOM.render(element,document.getElementById('root'))
组件
最简单的定义组件的方法就是定义一个函数:
- 接受props为参数
- 返回一个JSX
也可以使用class来定义一个组件
- 继承React.Component
- render()用于返回JSX
- constructor()
- 由于是继承了component基类,必须调用基类的constructor,并传入props:
super(props)
- 初始化state
- 由于是继承了component基类,必须调用基类的constructor,并传入props:
- 组件名称首字母大写
- 组件必须只返回一个根元素,一般用一个div来对内部元素进行包裹
- 函数式组件或者组件的render方法返回null,则此组件不会被渲染
props
父组件传递给子组件的数据。
- 组件永远不能修改props,是只读的。
state
setState 方法由父类 Component 所提供。当我们调用这个函数的时候,React.js 会更新组件的状态 state ,并且重新调用 render 方法,然后再把 render 方法所渲染的最新的内容显示到页面上。
setState异步性
当你调用 setState 的时候,React.js 并不会马上修改 state。而是把这个对象放到一个更新队列里面,稍后才会从队列当中把新的状态提取出来合并到 state 当中,然后再触发组件更新。
- 由于这种异步性,你不能依赖它们的值去计算下一个状态的state。
如:this.setState({ count: this.state.count + 1})
- 由于这种异步性,你不能依赖它们的值去计算下一个状态的state。
这里就要用上setState 的第二种使用方式,可以接受一个函数作为参数。React.js 会把上一个 setState 的结果传入这个函数,你就可以使用该结果进行运算、操作,然后返回一个对象作为更新 state 的对象:
this.setState((prevState) => {
return { count: prevState.count + 1 }
})
自上而下
state只被组件自己拥有,要想传递给其他的组件(子组件),就要使用props向下传递。
一个组件不知道其他组件是有状态的还是无状态的。组件间只是通过props来传递数据。
state与props区别
- props是父组件传递进来的数据,是只读的
- state是组件自己维护的状态,是可变的
共同点:
- 二者的变化都会触发组件的重新渲染
处理事件
与DOM中处理事件类似,但也有一些区别:
- React事件名使用驼峰命名法,而不是DOM中的小写形式
- 在JSX中为事件属性传入一个函数,而不是一个字符串。
<button onClick={activateLasers}>
Activate Lasers
</button>
- 不能通过返回false来取消默认行为,必须在事件处理函数中显式调用preventDefault。
- 当使用class来定义组件的时候,一个常用的模式就是定义class的方法来处理事件:
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// This binding is necessary to make `this` work in the callback
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}
- 在元素中使用为click事件定义处理函数:
onClick={this.handleClick}
,即class中的handleClick方法 - 在constructor的初始化工作中要注意为事件处理函数绑定this。在js中,class中的方法并不默认绑定this,因此要自己强制绑定,以保证在click发生时handleClick内this指向的是当前组件
也可以使用箭头函数来达到同样的目的:
class LoggingButton extends React.Component {
// This syntax ensures `this` is bound within handleClick.
handleClick = () => {
console.log('this is:', this);
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
);
}
}
列表
- 一般使用map()函数来遍历一个数组并生成对应的React元素:
const listItems = numbers.map((number) =>
<li>{number}</li>
);
可以使用{}
包含一个元素列表:
ReactDOM.render(
<ul>{listItems}</ul>,
document.getElementById('root')
);
key属性
最好为每一个列表中的元素分配一个key属性。
key属性帮助React识别哪些项被修改了,被添加了或者被移除了。
- 不要求key属性在全局唯一,只要在兄弟元素中唯一
- key属性对于React来说是一个重要信息,但是不会传递给你的组件。如果在组件中你需要同一个值,需要自己使用另一个prop属性去传递。
虚拟DOM
在React中,组件render方法得到的结果并不是真正的DOM节点,而是纯粹的js对象,用于描述DOM节点。我们称之为virtual DOM。
React之所以比直接操作DOM的JS库快,原因就是在渲染时,React 会把组件当前的虚拟DOM结构和前一次的虚拟DOM结构做比较,只有存在差异性,React才会把差异的内容同步到实体DOM上。
React速度快的原因,还有一个是它出色的Diff算法。标准的比较两棵树的Diff算法的时间复杂是 O(n3) 。而React基于非常符合实际场景的几个策略,就将Diff算法的时间复杂度降到了接近O(n)。这几个策略是:
- DOM 节点跨层级的移动操作特别少,可以忽略不计,因此对两棵树的比较分层进行,只会对同一层次的节点进行比较。
如果发现层内的一个节点已经不存在,则该节点及其子节点会被完全删除掉,不会用于进一步的比较。
- 如果两个组件或元素类型不同,那么他们就是完全不同的树,不需要再比较他们的子节点。
比如, 如果有个<Header>
被<ExampleBlock>
替换掉了,React 会删除掉 Header再创建一个 ExampleBlock. - 对同一层级的节点,diff算法提供了插入、移动和删除三种节点操作。
可以为组件或元素设置key属性,key用来标识这个组件或元素。一般多用于列表中。列表中key属性的存在,可以让React正确识别新增、修改和删除,从而做出最优的修改。
refs
ref属性用于获取组件的引用。
这里子组件必须是一个React component的实例或者一个DOM元素。
ref的应用场景一般是想要直接调用一个组件实例的方法,而不是通过传递新的props来使组件重新渲染.
一个典型的例子就是使某个input元素获得焦点:
class AutoFocusTextInput extends React.Component {
componentDidMount() {
this.textInput.focus();
}
render () {
return (
<input ref={(input) => this.textInput= input} />
)
}
}
可以看到我们给 input 元素加了一个 ref 属性,这个属性值是一个函数。当 input 元素在页面上挂载完成以后,React.js 就会调用这个函数,并且把这个挂载以后的 DOM 节点传给这个函数。在函数中我们把这个 DOM 元素设置为组件实例的一个属性,这样以后我们就可以通过 this.input 获取到这个 DOM 元素。
不能在函数式组件中使用refs,因为它们没有实例
但是可以在函数式组件内部的DOM元素或者class组件上可以使用。
props.children
我们可以在组件标签中编写内嵌的结构,就像普通的html标签一样:
<Card>
<h2>React.js 小书</h2>
<div>开源、免费、专业、简单</div>
订阅:<input />
</Card>
这样我们希望组件Card只是一个包含型的组件,其中的内容是随意的。
在Card组件中我们可以通过props.children获得嵌套在组件内部的JSX结构:
class Card extends Component {
render () {
return (
<div className='card'>
<div className='card-content'>
{this.props.children}
</div>
</div>
)
}
}
dangerouslySetHTML
根据之前的知识,我们知道在 React.js 当中所有的表达式插入的内容都会被自动转义。但有时我们想不通过转义,直接将传入的内容当做html结构显示在页面中,这时就需要dangerouslySetHTML属性,让我们可以动态设置元素的innerHTML。
render () {
return (
<div
className='editor-wrapper'
dangerouslySetInnerHTML={{__html: this.state.content}} />
)
}
style
React.js 中的元素的 style 属性的用法和 DOM 里面的 style 不大一样。
在React中需要把CSS属性变为一个对象再传给元素:
<h1 style={{fontSize: '12px', color: 'red'}}>React.js 小书</h1>
style 接受一个对象,这个对象里面是这个元素的 CSS 属性键值对,原来 CSS 属性中带 - 的元素都必须要去掉 - 换成驼峰命名,如fontSize、textAlign。
我们可以利用这种方式使用props或者state动态设置元素的样式。
PropTypes
React.js 就提供了一种机制,让你可以给组件的props加上类型验证,这样就能防止传入错误的数据类型导致出错。
PropTypes是一个第三方库,需要单独安装和引入。
class Comment extends Component {
static propTypes = {
comment: PropTypes.object
}
render () {
const { comment } = this.props
return (
<div className='comment'>
<div className='comment-user'>
<span>{comment.username} </span>:
</div>
<p>{comment.content}</p>
</div>
)
}
}
并且给 Comment 组件类添加了类属性 propTypes,并规定了comment类型必须为object。此时再传入不是object的值会报错。
通过isRequired也可以要求某一个参数必须传入:
static propTypes = {
comment: PropTypes.object.isRequired
}
PropTypes 提供了一系列的数据类型可以用来配置组件的参数:
PropTypes.array
PropTypes.bool
PropTypes.func
PropTypes.number
PropTypes.object
PropTypes.string
PropTypes.node
PropTypes.element
Element、Component与instance
Element:是一个纯粹的对象,用于描述一个DOM元素。
JSX用于描述Element,而Babel会将其转化为调用React.createElement的方式,最终结果就是生成一个Element。Component:是一个接受参数并返回Element的函数或者类。
- instance:当你调用
ReactDOM.render()
把一个组件渲染到一个具体的DOM元素中时,返回的值即使一个实例
生命周期
初始化阶段
getDefaultProps与getInitialState
当我们拥有ES6运行环境时,可以通过class语法定义组件。
但是without ES6的时候,就需要通过createReactClass()
函数来创建一个组件类。在这个函数中要使用getDefaultProps
与getInitialState
来定义props与state的初始值。
var Counter = React.createClass({
getDefaultProps: function() {
return {
title: 'Basic counter!!!'
}
},
getInitialState: function() {
return {
count: 0
}
},
render:function(){
……
},
propTypes: {
title: React.PropTypes.string
}
});
挂载阶段
我们把 React.js 将组件渲染,并且构造 DOM 元素然后塞入页面的过程称为组件的挂载。
componentWillMount与componentDidMount
在挂载前后我们可以有机会做一些处理:
-> constructor()
-> componentWillMount()
-> render()
// 构造 DOM 元素插入页面
-> componentDidMount()
- React会在组件render之前调用componentWillMount,在这个阶段调用this.setState()方法将不会触发重复渲染。
- React会在组件被渲染成DOM元素插入页面后调用componentDidMount,此时对应的DOM元素已经生成,这意味着这个方法是初始化其他需要访问DOM或操作数据的第三方库的最佳时机。
更新阶段
除了挂载阶段,还有一种“更新阶段”。说白了就是 state或者props更改导致 React.js 重新渲染组件并且把组件的变化应用到 DOM 元素上的过程,这是一个组件的变化过程。
当props更新时,这些生命周期函数会按以下顺序调用:
当state更新时,则会按以下顺序调用:
componentWillReceiveProps(nextProps)
组件从父组件接收到新的 props 之前调用。
在此方法内调用this.setState()将不会导致重复render。
shouldComponentUpdate(nextProps, nextState)
shouldComponentUpdate 允许我们手动地判断是否要进行组件更新,常作为优化React性能使用。
当shouldComponentUpdate返回false时,组件本次的render方法不会被触发。可以通过在这个方法中比较前后两次state或者props,根据实际业务场景决定是否需要触发render方法。
componentWillUpdate
组件重新渲染之前调用。
在这个函数内你不能调用setState改变组件状态。
componentDidUpdate
render()调用完毕,组件重新渲染完成,已经变更到真实的 DOM 以后调用。
卸载阶段
componentWillUnmount
同样在元素被从页面中删除的时候React也控制了这个组件的删除过程:
// 即将从页面中删除
-> componentWillUnmount()
// 从页面中删除
在从页面中删除前会调用componentWillUnmount方法。
可以在这个阶段进行一些清理工作,如计数器的清理等等。