死磕React 第一回
上帝视角
官网当然是我们最佳的学习资料,但我记得一句话,好老师从来都不是按照课本顺序来讲义的,所以对于官网上的文档,我觉得最佳的学习顺序也并非从头看到尾,我们首先要开启上帝视角
为什么要有react呢,他解决了一类什么问题?
React 理念这篇官网的文章让我找到了一些答案,水平有限,我只读懂了文章里的第一步、第二步,后面就开始晕了,建议第一次接触的人也不要急于全部搞懂,这篇文章给我两个很重要的信息。
- 前端分层解决方案——组件
第一次让我见识到,原来前端不再是像以前那样,写一大串html,来完成整个页面的静态编码,而是将页面切割成不同的部分,用组件来表示页面的元素 - 组件的实现方式——react
react是什么呢?我认为它就是组件的一种实现方式之一,组件是一种思想,落实到编码实现上,就是react了,react也就显的不再那么高深了,由此我还能猜想到Vue和Angular无非就是组件的另外两种实现方式而已
了解概念
接下来就要撸一堆概念了,建议撸完本文后,可以官网上撸概念
组件
将UI切分成一些的独立的、可复用的部件,这样你就只需专注于构建每一个单独的部件,这样的部件称为组件,在React中有其特有的语法表示,组件从概念上看就像是函数,它可以接收任意的输入值(称之为“props”),并返回一个需要在页面上展示的React元素
函数定义/类定义组件
定义一个组件最简单的方式是使用JavaScript函数:
function Welcome(props) {
return Hello, {props.name}
;
}
它接收一个单一的“props”对象并返回了一个React元素,可能你会对这句话里的props
和React元素
理解起来困难,没关系,就将其当成PHP里的参数和返回值好了。
你也可以使用 ES6 class 来定义一个组件:
class Welcome extends React.Component {
render() {
return Hello, {this.props.name}
;
}
}
上面两个组件在React中是相同的,我就当第一种是过程编程,第二种是对象编程吧,这样有助于我记忆,毕竟利用学过的知识来映射新的知识,这样能更清楚的记住
组合组件
组件可以在它的输出中引用其它组件,这就可以让我们用同一组件来抽象出任意层次的细节。在React应用中,按钮、表单、对话框、整个屏幕的内容等,这些通常都被表示为组件。
我们可以创建一个App
组件,用来多次渲染Welcome
组件:
function Welcome(props) {
return Hello, {props.name}
;
}
function App() {
return (
);
}
ReactDOM.render(
,
document.getElementById('root')
);
警告:组件的返回值只能有一个根元素。这也是我们要用一个
来包裹所有元素的原因。 元素
上面我们提到一个组件会返回一个React元素,所谓元素是构成 React 应用的最小单位,元素用来描述我们在屏幕上看到的内容:
const element =
Hello, world
;与浏览器的 DOM 元素不同,React 当中的元素事实上是普通的对象,React DOM 可以确保 浏览器 DOM 的数据内容与 React 元素保持一致
元素渲染到 DOM 中
首先我们在一个 HTML 页面中添加一个 id="root" 的
:
在此 div 中的所有内容都将由 React DOM 来管理,所以我们将其称之为 “根” DOM 节点。
我们将 元素传入一个名为 ReactDOM.render() 的方法来将其渲染到页面上:
const element =
Hello, world
; ReactDOM.render( element, document.getElementById('root') );Props&纯函数
上文中我们搁置了对Props的解释,只是暂且看作PHP函数的参数来理解,实际上也是如此。
对于函数声明的组件,props就是外部使用组件时,传入组件的参数
// Welcome组件通过props.name拿到调用者传入的数据 function Welcome(props) { return
Hello, {props.name}
; } // 组件App 返回一个react元素,元素中使用了Welcome组件,并将name属性传入组件 function App() { return (); } // 渲染App组件到DOM中 ReactDOM.render(, document.getElementById('root') ); /*output Hello, Sara
*/对于类声明的组件,props就是类的一个属性,通过这个属性能取到外部使用组件时,传入组件的一些必要数据
// Welcome组件通过this.props.name拿到调用者传入的数据 class Welcome extends React.Component { render() { return
Hello, {this.props.name}
; } } // 组件App 返回一个react元素,元素中使用了Welcome组件,并将name属性传入组件 function App() { return (); } // 渲染App组件到DOM中 ReactDOM.render(, document.getElementById('root') ); /*output Hello, Sara
*/当然这还不是全部,Props相比一般的参数,具有只读性,也就是其值不会被改变,这里正好引入了纯函数和非纯函数的概念
function sum(a, b) { return a + b; }
类似于上面的这种函数称为“纯函数”,它没有改变它自己的输入值,当传入的值相同时,总是会返回相同的结果
与之相对的是非纯函数,它会改变它自身的输入值:
function withdraw(account, amount) { account.total -= amount; }
React是非常灵活的,但它也有一个严格的规则:
所有的React组件必须像纯函数那样使用它们的propsState&生命周期
应用的界面是随时间动态变化的,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 (
); } } // 1. 渲染Clock组件 ReactDOM.render(Hello, world!
It is {this.state.date.toLocaleTimeString()}.
, 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