死磕React 第一回
上帝视角
官网当然是我们最佳的学习资料,但我记得一句话,好老师从来都不是按照课本顺序来讲义的,所以对于官网上的文档,我觉得最佳的学习顺序也并非从头看到尾,我们首先要开启上帝视角
为什么要有react呢,他解决了一类什么问题?
React 理念这篇官网的文章让我找到了一些答案,水平有限,我只读懂了文章里的第一步、第二步,后面就开始晕了,建议第一次接触的人也不要急于全部搞懂,这篇文章给我两个很重要的信息。
- 前端分层解决方案——组件
第一次让我见识到,原来前端不再是像以前那样,写一大串html,来完成整个页面的静态编码,而是将页面切割成不同的部分,用组件来表示页面的元素 - 组件的实现方式——react
react是什么呢?我认为它就是组件的一种实现方式之一,组件是一种思想,落实到编码实现上,就是react了,react也就显的不再那么高深了,由此我还能猜想到Vue和Angular无非就是组件的另外两种实现方式而已
了解概念
接下来就要撸一堆概念了,建议撸完本文后,可以官网上撸概念
组件
将UI切分成一些的独立的、可复用的部件,这样你就只需专注于构建每一个单独的部件,这样的部件称为组件,在React中有其特有的语法表示,组件从概念上看就像是函数,它可以接收任意的输入值(称之为“props”),并返回一个需要在页面上展示的React元素
函数定义/类定义组件
定义一个组件最简单的方式是使用JavaScript函数:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
它接收一个单一的“props”对象并返回了一个React元素,可能你会对这句话里的props
和React元素
理解起来困难,没关系,就将其当成PHP里的参数和返回值好了。
你也可以使用 ES6 class 来定义一个组件:
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
上面两个组件在React中是相同的,我就当第一种是过程编程,第二种是对象编程吧,这样有助于我记忆,毕竟利用学过的知识来映射新的知识,这样能更清楚的记住
组合组件
组件可以在它的输出中引用其它组件,这就可以让我们用同一组件来抽象出任意层次的细节。在React应用中,按钮、表单、对话框、整个屏幕的内容等,这些通常都被表示为组件。
我们可以创建一个App
组件,用来多次渲染Welcome
组件:
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')
);
警告:组件的返回值只能有一个根元素。这也是我们要用一个<div>来包裹所有<Welcome />元素的原因。
元素
上面我们提到一个组件会返回一个React元素,所谓元素是构成 React 应用的最小单位,元素用来描述我们在屏幕上看到的内容:
const element = <h1>Hello, world</h1>;
与浏览器的 DOM 元素不同,React 当中的元素事实上是普通的对象,React DOM 可以确保 浏览器 DOM 的数据内容与 React 元素保持一致
元素渲染到 DOM 中
首先我们在一个 HTML 页面中添加一个 id="root" 的 <div>:
<div id="root"></div>
在此 div 中的所有内容都将由 React DOM 来管理,所以我们将其称之为 “根” DOM 节点。
我们将 元素传入一个名为 ReactDOM.render() 的方法来将其渲染到页面上:
const element = <h1>Hello, world</h1>;
ReactDOM.render(
element,
document.getElementById('root')
);
Props&纯函数
上文中我们搁置了对Props的解释,只是暂且看作PHP函数的参数来理解,实际上也是如此。
对于函数声明的组件,props就是外部使用组件时,传入组件的参数
// Welcome组件通过props.name拿到调用者传入的数据
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
// 组件App 返回一个react元素,元素中使用了Welcome组件,并将name属性传入组件
function App() {
return (
<div>
<Welcome name="Sara" />
</div>
);
}
// 渲染App组件到DOM中
ReactDOM.render(
<App />,
document.getElementById('root')
);
/*output
<h1>Hello, Sara</h1>
*/
对于类声明的组件,props就是类的一个属性,通过这个属性能取到外部使用组件时,传入组件的一些必要数据
// Welcome组件通过this.props.name拿到调用者传入的数据
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
// 组件App 返回一个react元素,元素中使用了Welcome组件,并将name属性传入组件
function App() {
return (
<div>
<Welcome name="Sara" />
</div>
);
}
// 渲染App组件到DOM中
ReactDOM.render(
<App />,
document.getElementById('root')
);
/*output
<h1>Hello, Sara</h1>
*/
当然这还不是全部,Props相比一般的参数,具有只读性,也就是其值不会被改变,这里正好引入了纯函数和非纯函数的概念
function sum(a, b) {
return a + b;
}
类似于上面的这种函数称为“纯函数”,它没有改变它自己的输入值,当传入的值相同时,总是会返回相同的结果
与之相对的是非纯函数,它会改变它自身的输入值:
function withdraw(account, amount) {
account.total -= amount;
}
React是非常灵活的,但它也有一个严格的规则:
所有的React组件必须像纯函数那样使用它们的props
State&生命周期
应用的界面是随时间动态变化的,State可以在不违反props规则的情况下,根据用户操作、网络响应、或者其他状态变化,使组件动态的响应并改变组件的输出
我们在定义组件的时候,虽然可以通过函数和类声明这两种方式,但是当涉及到局部状态、生命周期钩子等高级用法时,只能通过类的方式来声明组件
官网的例子写的非常详细,细致的说出了其演变,我重新再拜读下官网例举的定时器例子,并试着给代码用1、2、3标注执行的顺序,加上注释
class Clock extends React.Component {
// 2. 组件被外部调用时,会优先执行构造函数,这里是初始化this.state的地方
constructor(props) {
super(props);
this.state = {date: new Date()};
}
// 4. 当组件返回的元素输出到屏幕时,调用此钩子设置一个定时器,每秒钟调用一次tick()
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
// 6. 一旦Clock组件被从DOM中移除,会调用这个钩子函数清除定时器
componentWillUnmount() {
clearInterval(this.timerID);
}
/* 5. 调用tick()方法,通过使用包含当前时间的对象调用setState()来调度UI更新。
通过调用setState() ,React 知道状态已经改变,并再次调用render()方法
来确定屏幕上应当显示什么。这一次,render()方法中的this.state.date将不同,
所以渲染输出将包含更新的时间,并相应地更新DOM。
*/
tick() {
this.setState({
date: new Date()
});
}
// 3. 随后render方法被调用,将Clock组件返回的元素输出到屏幕
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
// 1. 渲染Clock组件
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
我们可以在组件类上声明特殊的方法,当组件挂载或卸载时,来运行一些代码
上例中的componentDidMount
和componentWillUnmount
这些方法被称作生命周期钩子,分别在组件输出到 DOM 后、组件从DOM中移除时执行
正确地使用状态
关于 setState() 这里有三件事情需要知道
- 不要直接更新状态
例如,此代码不会重新渲染组件:
// Wrong
this.state.comment = 'Hello';
应当使用 setState():
// Correct
this.setState({comment: 'Hello'});
构造函数是唯一能够初始化 this.state 的地方。
- 状态更新可能是异步的
React 可以将多个setState()
调用合并成一个调用来提高性能。
因为 this.props
和 this.state
可能是异步更新的,你不应该依靠它们的值来计算下一个状态。
例如,此代码可能无法更新计数器:
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
要修复它,请使用第二种形式的 setState()
来接受一个函数而不是一个对象。 该函数将接收先前的状态作为第一个参数,将需要更新的值作为第二个参数:
// Correct
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
上方代码使用了箭头函数,但它也适用于常规函数:
// Correct
this.setState(function(prevState, props) {
return {
counter: prevState.counter + props.increment
};
});
- 状态更新合并
当你调用 setState() 时,React 将你提供的对象合并到当前状态。例如,你的状态可能包含一些独立的变量:
constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}
你可以调用 setState() 独立地更新它们:
componentDidMount() {
fetchPosts().then(response => {
this.setState({
posts: response.posts
});
});
fetchComments().then(response => {
this.setState({
comments: response.comments
});
});
}
这里的合并是浅合并,也就是说this.setState({comments})完整保留了this.state.posts,但完全替换了this.state.comments