第 1 章 第一个React Web应用程序
一、React组件
React组件是继承React.Component类的ES6类,render()是React组件唯一必需的方法,该方法的返回值是渲染到页面的内容。
class ProductList extends React.Component {
render() {
return (
<div className='ui unstackable items'>
Hello, friend! I am a basic React component.
</div>
);
}
}
ES6引入了类声明语法。ES6类是JavaScript基于原型的继承模型的语法糖。
声明React组件有两种方法:
- ES6类;
- 导入并使用
createReactClass()方法
import createReactClass from 'create-react-class';
const HelloWorld = createReactClass({
render() { return(<p>Hello, world!</p>) }
})
二、JSX和Babel
- JSX:JavaScript扩展语法,类HTML语法,会编译成原生JavaScript最终渲染为浏览器中显示的HTML。
// JSX
<div className='ui items'>
Hello, friend! I am a basic React component.
</div>
// 编译成JavaScript
React.createElement('div', {className: 'ui items'},
'Hello, friend! I am a basic React component.'
)
// 渲染HTML
浏览器的JavaScript解析器在遇到JSX时会出错,JSX是标准JavaScript的扩展。所以可以让浏览器的JavaScript解释器使用此扩展。
- Babel:JavaScript转译器,将ES6转译为ES5;把JSX编译成vanilla(原生) ES5 JS;通过设置type属性实现
// `type="text/babel"`表示需要Babel处理此脚本的加载,data-plugins指定使用的一个特殊的`Babel`插件
<script
type="text/babel"
data-plugins="transform-class-properties"
src="./js/app.js"
></script>
三、ReactDOM.render()方法
ReactDOM来自react-dom库,需要两个参数,第一个参数是需要渲染的组件(what),第二个参数是渲染组件的位置(where):
ReactDOM.render([what], [where]);
ReactDOM.render(
<ProductList />,
document.getElementById('content')
);
四、数据state和prop
state由组件拥有,在初始化组件时调用了constructor()函数定义初始值,使用this.setState()方法更改。props实现数据从父组件流向子组件,子组件通过this.props访问props,但无法修改它们。因为父组件拥有props并提供给子组件,子组件不是props的所有者,React支持单向数据流。props可以传递基本类型、JavaScript对象、原子操作、函数等,甚至还可以传递React元素和虚拟DOM。- 当组件的
state或props更新时,组件会重新渲染。
// 父组件
class ProductList extends React.Component {
constructor(props) {
super(props);
this.state = {
products: [],
};
}
componentDidMount() {
this.setState({ products: Seed.products });
}
render() {
const productComponents = this.state.products.map((product) => (
<Product
key={'product-' + product.id}
id={product.id}
title={product.title}
/>
));
return (
<div>{productComponents}</div>
);
}
}
// 子组件
class Product extends React.Component {
render() {
return (
<div>{this.props.title} </div>
);
}
}
在JSX中,大括号是一个分隔符,它向JSX发出信号,表明大括号之间的内容是JavaScript表达式。另一个分隔符是引号,它用来表示字符串,如id='1'。
对于
render()函数,React自动将this绑定到React组件类,所以当我们在组件内部编写this.props时,它将访问组件上的props属性。
五、事件传递
子组件不能修改prop,所以当事件触发时,需要向父组件传递,父组件进行数据修改。通过props传递函数是子组件与其父组件传递事件的标准方式。
// 父组件
class ProductList extends React.Component {
constructor(props) {
super(props);
this.state = {
products: [],
};
this.handleProductUpVote = this.handleProductUpVote.bind(this);
}
componentDidMount() {
this.setState({ products: Seed.products });
}
handleProductUpVote(productId) {
console.log(productId + ' was upvoted.');
}
render() {
const productComponents = this.state.products.map((product) => (
<Product
key={'product-' + product.id}
id={product.id}
title={product.title}
onVote={this.handleProductUpVote}
/>
));
return (
<div>{productComponents}</div>
);
}
}
// 子组件
class Product extends React.Component {
constructor(props) {
super(props);
this.handleUpVote = this.handleUpVote.bind(this);
}
handleUpVote() {
this.props.onVote(this.props.id);
}
render() {
return (
<div onClick={this.handleUpVote}>{this.props.title} </div>
);
}
}
执行步骤:
- onClick点击触发handleUpVote方法;
- handleUpVote方法通知this.props.onVote;
- 执行父组件handleProductUpVote()方法;
六、更新state和不变性
将state视为不可变的,只能使用this.setState()修改state,其他地方不能修改到state。
// 在ProductList组件内
handleProductUpVote(productId) {
const nextProducts = this.state.products.map((product) => {// nextProducts为新数组
if (product.id === productId) {
return Object.assign({}, product, {// 将属性克隆出来,不修改到product对象
votes: product.votes + 1,
});
} else {
return product;
}
});
this.setState({
products: nextProducts,
});
}
七、属性初始化器
属性初始化器简化了React类组件,能在Babel插件transform-class-properties中使用,
- 使用箭头函数来自定义组件方法,确保函数内部的
this能绑定到当前组件,可以删除constructor()函数,无须手动绑定调用;
handleUpVote = () => (
this.props.onVote(this.props.id)
);
- 在
constructor()函数之外定义初始状态
class ProductList extends React.Component {
state = {
products: [],
};
}