React学习 & 与Vue的比较总结
React文档学习
学习资源:react中文文档
前言
有过Vue与Vuex的学习基础;
在重新学习React的时候,与过去的Vue开发经历进行了简单比较,写下了这篇笔记
总结
重点:摆正心态
过去使用双向绑定的Vue进行开发,虽然对于Vue与Vuex的使用也不过皮毛,不过也确实感觉到了其中的简便;而对于React的第一印象便是数据单项绑定,一开始便有了畏难情绪。在这一次的文档学习中,理解了React所谓的单向数据流思想,感觉从某些角度看,它的存在还是比Vue有着一定优势的,因此调整心态还是挺重要的。
明天继续学习Redux,并且与Vuex进行比较。
目录
- 1、JSX文件类型:react组件文件格式
- 2、渲染:Virtual DOM渲染机制
- 3、组件与依赖:父组件传递给子组件的props属性
- 4、组件状态的作用:state与props的区别
- 5、组件状态与生命周期:生命周期钩子
- 6、事件处理
- 7、条件渲染
- 8、列表渲染
- 9、状态提升:通过父组件共享状态
- 10、终极实例:一个demo的代码分析
1、JSX文件类型:融合了js与html两种语言
const element = <h1>Hello, world!</h1>;
html段中可包含js语法
// 调用函数
function formatName(user) {
return user.firstName + ' ' + user.lastName;
}
const user = {
firstName: 'Harper',
lastName: 'Perez'
};
const element = (
<h1>
Hello, {formatName(user)}!
</h1>
);
// 设置标签属性
const element = <img src={user.avatarUrl}></img>;
在js中处理html段
function getGreeting(user) {
if (user) {
return <h1>Hello, {formatName(user)}!</h1>;
}
return <h1>Hello, Stranger.</h1>;
}
// 闭合式标签需要在结尾加/
const element = <img src={user.avatarUrl} />;
防止注入式攻击:
推荐嵌套写入数据:
const title = response.potentiallyMaliciousInput;
// This is safe:
const element = <h1>{title}</h1>;
// 完整示例
// 每秒更新html段
// react仅仅更新必要的部分,也就是变量部分
// 由此体现Virtual DOM tree的高效
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(
element,
document.getElementById('root')
);
}
setInterval(tick, 1000); // 每过一秒,重新传入render函数
2、渲染
const element = <h1>Hello, world</h1>;
// 传入ReactDOM.render方法进行渲染
ReactDOM.render(
element,
document.getElementById('root')
);
react元素在创建后是不可变的,更新的唯一方法是创建新元素,然后传入ReactDOM.render方法
3、组件与依赖
props属性可读,可用,不可修改
渲染
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
// react会将jsx属性作为一个对象props,传递给组件实例
// 组件的name属性必须大写开头
const element = <Welcome name="Sara" />;
ReactDOM.render(
element,
document.getElementById('root')
);
多次渲染
// 多次渲染该组件时
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
function App() {
return (
<div>
<Welcome name="Sara" />
<Welcome name="Cahal" />
<Welcome name="Edite" />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
组件的提取,细化,提高可复用性
// 提取前的comment组件,混乱,层次复杂
function Comment(props) {
return (
<div className="Comment">
<div className="UserInfo">
<img className="Avatar"
src={props.author.avatarUrl}
alt={props.author.name}
/>
<div className="UserInfo-name">
{props.author.name}
</div>
</div>
<div className="Comment-text">
{props.text}
</div>
<div className="Comment-date">
{formatDate(props.date)}
</div>
</div>
);
}
// 提取后的comment组件
function Avatar(props) {
return (
<img className="Avatar"
src={props.user.avatarUrl}
alt={props.user.name}
/>
);
}
function UserInfo(props) {
return (
<div className="UserInfo">
<Avatar user={props.user} />
<div className="UserInfo-name">
{props.user.name}
</div>
</div>
);
}
function Comment(props) {
return (
<div className="Comment">
<UserInfo user={props.author} />
<div className="Comment-text">
{props.text}
</div>
<div className="Comment-date">
{formatDate(props.date)}
</div>
</div>
);
}
4、组件状态的作用
以时钟为例 ===> 利用组件状态优化
// 按照以下写法,需要对Clock实例添加单独的实现,而不是属于Clock组件的自身方法,需要多次渲染Clock实例
// 理想状态下,应该是渲染一次(调用ReactDOM.render())即可,生成的Clock实例会更新自身
function Clock(props) {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {props.date.toLocaleTimeString()}.</h2>
</div>
);
}
function tick() {
ReactDOM.render(
<Clock date={new Date()} />,
document.getElementById('root')
);
}
setInterval(tick, 1000);
5、组件状态与生命周期
状态(state):组件的私有属性,是局部的,完全受控于当前组件;
属性(JSX属性):属性是被传入的,只可读不可修改的;
生命周期:在重新渲染组件前,释放组件所占用的资源(DOM、计时器、eventListener)
函数组件 ==> 类 :使组件拥有状态,拥有生命周期
// 1、扩展为 React.Component 的ES6 类
class Clock extends React.Component {
// 2. 添加类构造函数来初始化状态this.state,用来替代原本的props
constructor(props) {
super(props); // 类组件应始终使用props调用基础构造函数。
this.state = {date: new Date()}; // 不再需要从外部传入jsx属性
}
// 2、 将函数组件中的函数体移动到render方法中
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
// 3、添加生命周期钩子
// 当组件输出到 DOM 后会执行 componentDidMount() 钩子
// 挂载 当dom中添加了该元素,就挂载定时器,用于更新时间
componentDidMount() {
this.timerID = setInterval(
() => this.tick()
,1000);
}
// 在卸载组件时,连同计时器一并卸载
// 在卸载组件前执行componentWillUnmount()函数
componentWillUnmount() {
clearInterval(this.timerID);
}
// tick方法:调用setState更新组件的局部状态,来调度UI更新
tick() {
this.setState({
date: new Date()
});
}
}
// 最终达到我们的理想效果,一次渲染即可
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
关于setState()
- 通过对象属性更新,并不会重新渲染
this.state.comment = 'Hello'; // 此代码不会重新渲染组件
// 应当使用setState
- 状态更新可能是异步的
react可将多个setState()合并;所以不要依赖他们的只进行计算……可能用到的是脏值
// 此代码无法用于实时更新的state,因为具体的set时间未知……
this.setState({
counter: this.state.counter + this.props.increment,
});
// 对应修复的方法:**向setState传入函数**,而不是对象
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
- 状态合并更新
constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}
this.setState({comments}) // 保留this.state.posts,但替换了this.state.comments
自顶向下单项数据流: 也就是react的特色(与vue的双向通信大不相同)
任何状态始终由某些特定(上层)组件所有,并且从该状态导出的任何数据或 UI 只能影响树中下方的组件。
6、事件处理
事件属性:驼峰式命名法
jsx语法:传入函数作为事件处理函数,如:onclick={activate}
;而不是字符串形式,如onclick="activateLasers()"
类的监听事件默认不会绑定this,因此添加事件绑定时
方法1:需要手动bind(this)
this.handleClick = this.handleClick.bind(this);
方法2:使用箭头函数
handleClick = () => {
console.log('this is:', this);
}
7、条件渲染
- if:判断分支渲染
- && : true && expression 总是返回 expression,而 false && expression 总是返回 false。
{unreadMessages.length > 0 &&
<h2>
You have {unreadMessages.length} unread messages.
</h2>
}
- 阻止组件渲染: render() { return null } 但不影响生命周期
8、列表渲染
原始渲染
// 简单粗暴的渲染方法,手动生成html代码段
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li>{number}</li>
);
// listItems包含了多个li标签
ReactDOM.render(
<ul>{listItems}</ul>,
document.getElementById('root')
);
优化列表组件渲染
// 创建基础列表组件 只需传入列表数组,即可自动生成
// ## 使用li标签必须添加一个key值,主要用于在li的一系列标签中唯一标识该元素,用于后续的删除、修改操作
// key的作用域是同级节点之间
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number,index) =>
// 当数组元素没有确定的id用于标识,可以用index替代
<li key={index}>{number}</li>
);
return (
<ul>{listItems}</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
9、状态提升
目的:用于父组件中使用多个子组件,则将子组件之间的共享状态提升到父组件中进行管理
问题:子组件之间的共享状态state保存到父组件中,子组件通过props读取,但是不能修改;
解决途径:为了使子组件能够修改提升后的共享状态,将父组件的方法作为props属性值传入子组件来实现;子组件借用父组件的方法来修改父组件的state,“虽然很麻烦,但是有助于查找bug”,在今后的实践中检验一下这句官网的话....
10、终极实例
在看完这一个代码之后,可以很明显的感觉到Vue与React的不同;React更加注重细化提取子组件,在单文件中定义一个多级组件,而Vue仅仅定义单个组件,然后引用其他....
而react的组件代码虽然真的很长很长,但是耐心的看来下,结构还是比较清晰的,重要的是要先分清组件之间的层级关系,还有共享数据的存放位置;
在以下这一个“假双向通信”的组件中,包含组件的层级关系表现为 :
FilterableProductTable
/\
/ \
SearchBar ProductTable
/\
/ \
ProductCategoryRow ProductRow
其中FilterableProductTable是顶级组件,保存state,将setState分发给SearchBar;而ProductTable以及它下属的ProductCategoryRow与ProductRow仅仅是获取state进行数据更新
class ProductCategoryRow extends React.Component {
render() {
return (<tr><th colSpan="2">{this.props.category}</th></tr>);
}
}
class ProductRow extends React.Component {
render() {
var name = this.props.product.stocked ?
this.props.product.name :
<span style={{color: 'red'}}>
{this.props.product.name}
</span>;
return (
<tr>
<td>{name}</td>
<td>{this.props.product.price}</td>
</tr>
);
}
}
class ProductTable extends React.Component {
render() {
var rows = [];
var lastCategory = null;
console.log(this.props.inStockOnly)
this.props.products.forEach((product) => {
if (product.name.indexOf(this.props.filterText) === -1 || (!product.stocked && this.props.inStockOnly)) {
return;
}
// 为不同的category创建一个ProductCategoryRow,每遍历一次都为当前category的所有product创建一个新ProductRow
// ProductCategoryRow与ProductRow 属于平级元素,都是ProductTable的td标签元素
if (product.category !== lastCategory) {
rows.push(<ProductCategoryRow category={product.category} key={product.category} />);
}
rows.push(<ProductRow product={product} key={product.name} />);
lastCategory = product.category;
});
return (
<table>
<thead>
<tr>
<th>Name</th>
<th>Price</th>
</tr>
</thead>
<tbody>{rows}</tbody>
</table>
);
}
}
class SearchBar extends React.Component {
constructor(props) {
super(props);
this.handleFilterTextInputChange = this.handleFilterTextInputChange.bind(this);
this.handleInStockInputChange = this.handleInStockInputChange.bind(this);
}
handleFilterTextInputChange(e) {
this.props.onFilterTextInput(e.target.value);
}
handleInStockInputChange(e) {
this.props.onInStockInput(e.target.checked);
}
render() {
return (
<form>
<input
type="text"
placeholder="Search..."
value={this.props.filterText}
onChange={this.handleFilterTextInputChange}
/>
<p>
<input
type="checkbox"
checked={this.props.inStockOnly}
onChange={this.handleInStockInputChange}
/>
{' '}
Only show products in stock
</p>
</form>
);
}
}
class FilterableProductTable extends React.Component {
constructor(props) {
super(props);
this.state = {
// 关键字搜索
filterText: '',
// checkbox状态
inStockOnly: false
};
this.handleFilterTextInput = this.handleFilterTextInput.bind(this);
this.handleInStockInput = this.handleInStockInput.bind(this);
}
handleFilterTextInput(filterText) {
this.setState({
filterText: filterText
});
}
handleInStockInput(inStockOnly) {
this.setState({
inStockOnly: inStockOnly
})
}
render() {
return (
<div>
<SearchBar
filterText={this.state.filterText}
inStockOnly={this.state.inStockOnly}
onFilterTextInput={this.handleFilterTextInput}
onInStockInput={this.handleInStockInput}
/>
<ProductTable
products={this.props.products}
filterText={this.state.filterText}
inStockOnly={this.state.inStockOnly}
/>
</div>
);
}
}
var PRODUCTS = [
{category: 'Sporting Goods', price: '$49.99', stocked: true, name: 'Football'},
{category: 'Sporting Goods', price: '$9.99', stocked: true, name: 'Baseball'},
{category: 'Sporting Goods', price: '$29.99', stocked: false, name: 'Basketball'},
{category: 'Electronics', price: '$99.99', stocked: true, name: 'iPod Touch'},
{category: 'Electronics', price: '$399.99', stocked: false, name: 'iPhone 5'},
{category: 'Electronics', price: '$199.99', stocked: true, name: 'Nexus 7'}
];
ReactDOM.render(
<FilterableProductTable products={PRODUCTS} />,
document.getElementById('container')
);