第一章 初入React世界
重点词汇
- 专注视图层
React并不是完整的MVC/MVVM框架,它专注于提供清晰、简介的view层解决方案。而又和模板引擎不同,因为它又是一个包括view和controller的库。
- Virtual DOM
我们都知道DOM操作非常昂贵。在前端开发中,性能消耗最大的就是DOM操作。React把真实DOM树转换成JavaScript对象树,每次数据更新后,重新计算Virtual DOM,并和上次生成的VD作对比买对发生变化的部分做批量更新
- 函数式编程
构建一个规则使计算重复被利用。React充分利用很多函数式方法减少用于代码。此外它本身就是简单函数,所以易于测试。可以说函数式编程才是React的精髓
- JSX
jsx的官方定义是类XML语法的ECMAScript扩展。它完美地利用了js自带的语法和特性,并使用大家熟悉的HTML语法来穿件元素。
例如:
const List = () => (
<div>
<Title>This is a title</Title>
<ul>
<li>list item</li>
<li>list item</li>
<li>list item</li>
</ul>
</div>
)
复制代码
* 注意:定义标签时,只允许被一个标签包裹;标签一定要闭合;定义组件时命名应该使用大写字母开头!!!
元素属性:js中的class和for均为关键字,class->className;for->htmlFor,boolean属性例如:
<Checkbox checked={true} />
// 可以简写为
<Checkbox checked />
//反之checked为false就可以省略不写
<Checkbox checked={false}/>
//不知道props里与有什么就最好不要设置它,使用es6的rest/spread特性来提高效率
<Component {...data}>
复制代码
- React组件
代码实例详见p14-p15
1、基本的封装性。
2、简单的生命周期呈现
3、明确的数据流动。根据不同的参数得到不同的响应,从而得出不同的渲染结果。
在这个阶段,前端在应用级别并没有过多复杂的交互,组件化发展缓慢。这样的逻辑一旦复杂起来,就会存在大量的dom操作,开发和维护成本相当高。
- React组件的构建
React的本质就是关心元素的构成,它基本上由3个部分组成-----属性(props)、状态(state)以及生命周期方法
- React组件的构建方法:1、使用React.createClass构建的组件时React最传统、也是兼容性最好的方法,例子:
const Button = React.createClass({
getDefaultProps() {
return {
color: 'blue',
text: 'Confirm'
}
}
render() {
const {text,color} = this.props;
return (
<button className={`btn btn-${color}`}>
<em>{text}</em>
</button>
)
}
})
复制代码
2、ES6 classes
import React,{Component} from 'react';
class Button extends Component {
static defaultProps = {
color: 'blue',
text: 'Confirm'
}
render() {
const {color,text} = this.props;
return (
<button className={`btn btn-${color}`}>
<em>{text}</em>
</button>
)
}
}
复制代码
在React的开发中,常用的方式是将组件拆分到合理的粒度,用组合的方式合成业务组件,也就是HAS-A关系。
* React的多有组件都继承自顶层React.Compoennt。它只是初始化了ReactComponent方法,声明了props、context、refs等,并在原型上定义了setState和forceUpdate方法。
3、无状态函数
无状态组件之传入props和context两个参数,不存在state,也没有生命周期方法。在适合的情况下,我们都应该切且必须使用无状态组件。
- React的数据流
在React中,数据是自顶向下单向流动的。这条规则让组件之间的关系变得简单可测。
- 内部的状态同意成为state。值得注意的是,setState是一个异步的方法,一个生命周期内所有的setState方法都会结并操作。我们并不推荐开发者滥用state,过多的内部状态会让数据流混乱,程序变得难以维护。
- props 是React中用来让组件之间相互联系的的一种机制,就像方法的参数一样。对于一个tabs组件它的props会有那些呢,1、className,根节点class方便覆盖原始样式;
2、classPrefix:class的前缀,对于组件来说,定义一个统一的class前缀,对样式与交互分离起了重要的作用
3、defaultActiveIndex和activeIndex
4、onChange:回调函数
- 子组件prop 在React中有一个重要且内置的prop-children,它代表组件的子组件合计 p25
render() {
return (<div>{this.getTabPanes()}</div>)
}
复制代码
这种调用方法叫做动态子组件(Dynamic Children)
- 组件prop(component prop)
可以将子组件以props的形式传递 - 用function prop与父组件通信 this.props.onChange({activeIndex, preIndex})
父组件将方法传递给子组件,子通过触发onChange给父亲回调需要的值 - propTypes propTypes用来规范props类型的与必须的状态,常见的有(string、number、func、array、bool、oneOfType、node)
React的生命周期
React的组件的生命周期可以分为挂在、渲染和卸载这几个阶段。大体我们可以分为两类:1、当组件在挂在或卸载时;2、当组件接受新的数据时
- 组件的挂载 componentDidMount componentWillMount
- 组件的卸载 compoentWillUnmount
- 数据的更新过程
class App extends React.Component {
componentWillReceiveProps(nextProps) {
}
shouldComponentUpdate(nextProps, nextState) {
}
componentWillUpdate(nextProps,nextState) {
}
componentDidUpdate(prevProps, prevState) {
}
}
复制代码
如果组件发生自身更新那么次执行scu,cwu,render,cdu
shouldComponentUpdate是一个特别的方法,接受props和state让开发者判断其是否需要更新。
* 值得注意的是stateless组件没有生命周期方法,也意味着每次都会重新渲染。我们可以使用Recompose库的pure方法:const OptimizedComponent = pure(ExpensiveComponent)
整体流程图
- ReactDOM 主要方法findDOMNode(获取真实dom元素)、render(渲染VirtualDom到dom)、unmountComponentAtNode卸载操作
- React之外的dom操作
React提供了时间绑定功能,单是仍然有一些特殊的情况需要自行绑定事件,例如Popup,React中使用DOM最多的还是极端DOM尺寸,例如:
function width(el) {
const styles = el.ownerDocument.defaultView.getComputedStyle(el,null);
const width = parseFloat(styles.width.indexOf('px') !== -1 ? styles.width : 0);
const boxSizing = styles.boxSizing || 'content-box';
if(boxSizing) {
return width;
}
const borderLeftWidth = parseFloat(styles.borderLeftWidth);
const borderRightWidth = parseFloat(styles.borderRightWidth);
const paddingLeft = parseFloat(styles.paddingLeft);
const paddingRight = parseFloat(styles.paddingRight);
return width - borderRightWidth - borderLeftWidth - paddingLeft - paddingRight;
}
}
复制代码
文末奉献一版手打tab组件 (Tabs、TabNav、TabContent)
//Tabs
class Tabs exntends Component {
static propTypes = {
className: PropTypes.string, //主节点上增加可选class
classPrefix: PropTypes.string, //class前缀
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropsTypes.node),
PropTypes.node
]),
// 默认激活索引,组件内更新
defaultActiveIndex: PropTypes.number,
// 默认激活索引,组件外更新
activeIndex: PropTypes.number,
// 切换时回调函数
onChange: PropTypes.func,
}
static defaultProps = {
classPrefix: 'tabs',
onChange: () => {},
};
constructor(props) {
super(props);
// 对事件方法的绑定
this.handleTabClick = this.handleTabClick.bind(this);
const currProps = this.props;
let activeIndex;
// 初始化 activeIndex state
if ('activeIndex' in currProps) {
activeIndex = currProps.activeIndex;
} else if ('defaultActiveIndex' in currProps) {
activeIndex = currProps.defaultActiveIndex;
}
this.state = {
activeIndex,
prevIndex: activeIndex,
};
}
componentWillReceiveProps(nextProps) {
// 如果 props 传入 activeIndex,则直接更新
if ('activeIndex' in nextProps) {
this.setState({
activeIndex: nextProps.activeIndex,
});
}
}
handleTabClick(activeIndex) {
const prevIndex = this.state.activeIndex;
// 如果当前 activeIndex 与传入的 activeIndex 不一致,
// 并且 props 中存在 defaultActiveIndex 时,则更新
if (this.state.activeIndex !== activeIndex &&
'defaultActiveIndex' in this.props) {
this.setState({
activeIndex,
prevIndex,
});
// 更新后执行回调函数,抛出当前索引和上一次索引
this.props.onChange({ activeIndex, prevIndex });
}
}
renderTabNav() {
const { classPrefix, children } = this.props;
return (
<TabNav
key="tabBar"
classPrefix={classPrefix}
onTabClick={this.handleTabClick}
panels={children}
activeIndex={this.state.activeIndex}
/>
);
}
renderTabContent() {
const { classPrefix, children } = this.props;
return (
<TabContent
key="tabcontent"
classPrefix={classPrefix}
panels={children}
activeIndex={this.state.activeIndex}
/>
);
}
render() {
const { className } = this.props;
// classnames 用于合并 class
const classes = classnames(className, 'ui-tabs');
return (
<div className={classes}>
{this.renderTabNav()}
{this.renderTabContent()}
</div>
);
}
}
复制代码
//TabsNav
class TabNav extends Component {
static propTypes = {
classPrefix: React.PropTypes.string,
panels: PropTypes.node,
activeIndex: PropTypes.number,
};
getTabs() {
const { panels, classPrefix, activeIndex } = this.props;
return React.Children.map(panels, (child) => {
if (!child) { return; }
const order = parseInt(child.props.order, 10);
// 利用 class 控制显示和隐藏
let classes = classnames({
[`${classPrefix}-tab`]: true,
[`${classPrefix}-active`]: activeIndex === order,
[`${classPrefix}-disabled`]: child.props.disabled,
});
let events = {};
if (!child.props.disabled) {
events = {
onClick: this.props.onTabClick.bind(this, order),
};
}
const ref = {};
if (activeIndex === order) {
ref.ref = 'activeTab';
}
return (
<li
role="tab"
aria-disabled={child.props.disabled ? 'true' : 'false'}
aria-selected={activeIndex === order? 'true' : 'false'}
{...events}
className={classes}
key={order}
{...ref}
>
{ child.props.tab}
</li>
);
});
}
render() {
const { classPrefix } = this.props;
const rootClasses = classnames({
[`${classPrefix}-bar`]: true,
});
const classes = classnames({
[`${classPrefix}-nav`]: true,
});
return (
<div className={rootClasses} role="tablist">
<ul className={classes}>
{this.getTabs()}
</ul>
</div>
);
}
}
复制代码
//TabContent
class TabContent extends Component {
static propTypes = {
classPrefix: React.PropTypes.string,
panels: PropTypes.node,
activeIndex: PropTypes.number,
};
getTabPanes() {
const { classPrefix, activeIndex, panels } = this.props;
return React.Children.map(panels, (child) => {
if (!child) { return; }
const order = parseInt(child.props.order, 10);
const isActive = activeIndex === order;
return React.cloneElement(child, {
classPrefix,
isActive,
children: child.props.children,
key: `tabpane-${order}`,
});
});
}
render() {
const { classPrefix } = this.props;
const classes = classnames({
[`${classPrefix}-content`]: true,
});
return (
<div className={classes}>
{this.getTabPanes()}
</div>
);
}
}
复制代码
// TabPane
class TabPane extends Component {
static propTypes = {
tab: PropTypes.oneOfType([
PropTypes.string,
PropTypes.node,
]).isRequired,
order: PropTypes.string.isRequired,
disable: PropTypes.bool,
isActive: PropTypes.bool,
};
render() {
const { classPrefix, className, isActive, children } = this.props;
const classes = classnames({
[className]: className,
[`${classPrefix}-panel`]: true,
[`${classPrefix}-active`]: isActive,
});
return (
<div
role="tabpanel"
className={classes}
aria-hidden={!isActive}>
{children}
</div>
);
}
}
复制代码
小结
随着章节的深入,我们会陆续介绍 React 高阶使用方法、背后的运行机制、处理数据的架构 Flux 与 Redux。相信从现在开始,你已经做好在 React 的海洋里遨游的准备了。