壹 ❀ 引

从零开始的react入门教程(一)一文中,我们搭建了第一个属于自己的react应用,并简单学习了jsx语法。jsx写法上与dom标签高度一致,当然我们也知道,本质上这些react元素都是React.createElement()的语法糖,通过编译,bable会将其还原成最原始的样子,比如如下代码效果相同:

  1. <div class="echo"></div>
  2. // 等同于
  3. React.createElement(
  4. 'div',
  5. {className: 'echo'}
  6. )

至少从书写上,jsx为我们提供了极大便利。在文章结尾,我们也敲到了react元素并非组件,它可能是一个单一的标签,也可能是一个代码块,在react中有专门的方式来创建组件,那么本文就从组件说起。

贰 ❀ 组件

贰 ❀ 壹 函数组件

react中的组件分为函数组件class组件,两者有一定区别,但都非常好理解。函数组件很简单,比如我们现在想复用一段简单的dom结构,但它的文本内容可能会不同,这种情况我们想到的就是文本内容是一个变量,这样就能做到dom复用的目的了,所以函数组件就是做了这样一件事:

  1. // 这是一个函数组件,它接受一些props,并返回组合后的dom结构
  2. function Echo(props) {
  3. return <div>听风是风又叫{props.name}</div>;
  4. };
  5. ReactDOM.render(<Echo name="听风是风" />, document.getElementById("root"));

需要注意的是,函数组件的函数名是大写的(class组件也是如此),我们在render中使用了组件Echo,并传递了一个name属性,所有在组件上传递的属性都会被包裹在props对象中,所以通过props参数我们能访问到每一个传递给组件的属性。通过打印props可以看到它是一个对象:

  1. function Echo(props) {
  2. console.log(props);
  3. return <div>听风是风又叫{props.name}</div>;
  4. };

传递的数据格式除了字符,数字,它当然也支持对象传递,比如下面这个例子运行结果与上方相同:

  1. const myName = {
  2. name:'echo'
  3. };
  4. function Echo(props) {
  5. console.log(props);
  6. return <div>听风是风又叫{props.name.name}</div>;
  7. };
  8. //
  9. ReactDOM.render(<Echo name= {myName}/>, document.getElementById("root"));

我们来解读下props.name.name,首先我们是将myName作为name的值传递给了组件,所以要访问到myName得通过props.name拿到,之后才是name取到了具体的值。其次需要注意的是传递对象需要使用{}包裹,如果不加花括号会有错误提示,jsx这里只支持加引号的文本或者表达式,而{myName}就是一个简单的表达式。

我们在上文中,也有将react元素赋予给一个变量的写法,比如:

  1. const ele = <div>我的名字是听风。</div>
  2. ReactDOM.render(ele, document.getElementById("root"));

其实组件也能像这样赋予给一个变量,所以看到下面这样的写法也不要奇怪:

  1. function Echo(props) {
  2. return <div>我的名字是{props.name}</div>;
  3. };
  4. // 这里将组件赋予给了一个变量,所以render时直接用变量名
  5. const ele = <Echo name="听风是风"/>
  6. ReactDOM.render(ele, document.getElementById("root"));

同理,react元素可以组合成代码块,组件同样可以组合成一个新组件,比如:

  1. function Echo(props) {
  2. return <div>我的名字是{props.name}</div>;
  3. };
  4. function UserList() {
  5. return (
  6. // 注意,只能有一个父元素,所以得用一个标签包裹
  7. <div>
  8. <Echo name="echo" />
  9. <Echo name="听风" />
  10. </div>
  11. );
  12. };
  13. ReactDOM.render(<UserList />, document.getElementById("root"));

在这个例子中,组件Echo作为基础组件,重新构建出了一个新组建UserList,所以到这里我们可以发现,组件算自定义的react元素,它可能是由react元素组成,也可能是由组件构成。

贰 ❀ 贰 class组件

除了上面的函数组件外,我们还可以通过class创建组件,没错,就是ES6的class,看一个简单的例子:

  1. class Echo extends React.Component {
  2. render() {
  3. return <div>我的名字是{this.props.name}</div>;
  4. }
  5. };
  6. ReactDOM.render(<Echo name="听风" />, document.getElementById("root"));

由于ReactDOM.render这一块代码相同,我们把目光放在class创建上,事实上这里使用了extends继承了Component类,得到了一个新组件Echo。由于此时的Echo并不是函数,也不能接受函数形参,但事实上我们可以通过this.props直接访问到当前组件所传递的属性,让人心安的是,与函数组件相比,我们同样有方法获取外部传递的props。

extends中的render方法是固定写法,它里面包含的是此组件需要渲染的dom结构,如果你了解过ES6的class类,除了render固有方法外,其实我们可以在这个类中自定义任何我们想要的属性以及方法,比如:

  1. const o = {
  2. a: 1,
  3. b: 2,
  4. };
  5. class Echo extends React.Component {
  6. // 这是一个自定义方法
  7. add(a, b) {
  8. return a + b;
  9. }
  10. // 这是固定方法
  11. render() {
  12. return <div>{this.add(this.props.nums.a, this.props.nums.b)}</div>;
  13. }
  14. }
  15. ReactDOM.render(<Echo nums={o} />, document.getElementById("root"));

看着似乎有点复杂,我们来解释做了什么,首先我们在外部定义了一个包含2个数字的对象o,并将其作为nums属性传递给了组件Echo,在组件内除了render方法外,我们还自定义了一个方法add,最终渲染的文本由此方法提供,所以我们在返回的标签中调用了此方法。前面说了可以通过this.props访问到外部传递的属性,所以这里顺利拿到了函数的两个实参并参与了计算。

那么到这里我们知道,除了一些组件固有方法属性外,我们也可以定义自己的方法用于处理渲染外的其它业务逻辑。

举个很常见的情景,在实际开发中,有时候我们处理的组件结构会相对庞大和复杂,这时候我们就能通过功能拆分,将一个大组件在内部拆分成单个小的功能块,比如下面这段代码:

  1. class Echo extends React.Component {
  2. handleRenderTop() {
  3. return "我是头部";
  4. }
  5. // 自定义的render方法
  6. renderTop() {
  7. return <div>{this.handleRenderTop()}</div>;
  8. }
  9. handleRenderMiddle() {
  10. // dosomething
  11. }
  12. renderMiddle() {
  13. return <div>我是中间部分</div>;
  14. }
  15. handleRenderBottom() {
  16. // dosomething
  17. }
  18. renderBottom() {
  19. return <div>我是底部</div>;
  20. }
  21. // 官方提供的固定render方法
  22. render() {
  23. return (
  24. <div>
  25. {this.renderTop()}
  26. {this.renderMiddle()}
  27. {this.renderBottom()}
  28. </div>
  29. );
  30. }
  31. }
  32. ReactDOM.render(<Echo />, document.getElementById("root"));

在上述代码中,假设这个组件结构和逻辑比较复杂,通过拆分我们将其分为了上中下三个部分,并创建了对应的处理方法,最终在render中我们将其组成在一起,这样写的好处是可以让组件的结构更清晰,也利于后期对于代码的维护。当然这里也只是提供了一种思路和可能性,具体做法还需要自行探索。

其实在class组件中除了固有render方法外,还有ES6的constructor,以及组件生命周期函数,这些都是固定写法,不过我们现在不急,后面会展开说明。

肆 ❀ props与State

肆 ❀ 壹 基本概念与区别

与vue双向数据绑定不同,react提供的是单向数据流,我们可以将react的数据流动理解成一条瀑布,水流(数据)从上往下流动,传递到了瀑布中的每个角落(组件),而这里的水流其实就是由props和State构成,数据能让看似静态的组件换发新生,所以现在我们来介绍下组件中的数据props与State,并了解它们的关系以及区别。

先说props,其实通过前面的例子我们已经得到,props就是外部传递给组件的属性,在函数组件中,可以直接通过形参props访问,而在class组件中,我们一样能通过this.props访问到外部传递的属性。

那么什么是State呢,说直白点,State就是组件内部定义的私有属性,这就是两者最直观的区别。

State在react中更官方的解释是状态机,状态的变化会引起视图的变化,所以我们只需要修改状态,react会自动帮我们更新视图。比如下面这个例子:

  1. class Echo extends React.Component {
  2. constructor(props) {
  3. // 参照ES6,extends时,constructor内使用this必须调用super,否则报错
  4. super(props);
  5. this.state = { name: "echo" };
  6. }
  7. render() {
  8. return (
  9. <div>
  10. 我的名字是{this.state.name},年龄{this.props.age}
  11. </div>
  12. );
  13. }
  14. }
  15. ReactDOM.render(<Echo age="27" />, document.getElementById("root"));

在上述例子中,外部传递的age就是props,所以在内部也是通过this.props访问,而内部定义的属性则是通过this.state声明,一个在里一个在外,它们共同构成了组件Echo的数据。

上述例子中constructor内部调用了super方法,这一步是必要的,如果你想在继承类的构造方法constructor中使用this,你就一定得调用一次,这也是ES6的规定,简单复习下ES6的继承:

  1. class Parent {
  2. constructor(x, y) {
  3. this.x = x;
  4. this.y = y;
  5. }
  6. }
  7. class Child extends Parent {
  8. constructor(x, y, z) {
  9. // 本质上就是调用了超类
  10. super(x, y);
  11. this.z = z; // 正确
  12. }
  13. say() {
  14. console.log(this.x, this.y, this.z);
  15. }
  16. }
  17. const o = new Child(1, 2, 3);
  18. console.log(o);
  19. o.say(); //1,2,3

首先我们定义了一个父类Parent,在它的构造方法中定义了x,y两个属性,之后我们通过extends让Child类继承了Parent,并在Child的构造方法中执行了super,这里本质上其实就是调用了父类Parent的构造函数方法,只是在执行superthis指向了Child实例,这也让Child实例o顺利继承了Parent上定义的属性。我们可以输出实例o,可以看到它确实继承了来着Parent的属性。

super本意其实就是超类,所有被继承的类都可以叫超类,也就是我们长理解的父类,它并不是一个很复杂的概念,这里就简单复习下。

肆 ❀ 贰 不要修改props以及如何修改props

在上文中,我们介绍了props与State的基本作用与区别,一个组件可以在内部定义自己需要的属性,也可以接受外部传递的属性。事实上,比如父子组件结构,父组件定义的State也能作为props传递给子组件使用,只是对于不同组件它的含义不同,这也对应了上文瀑布的比喻,水流由props与State构成就是这个意思了。

我们说State是私有属性,虽然它可以传递给其它组件作为props使用,但站在私有的角度,我虽然大方的给你用,那你就应该只使用而不去修改它,这就是props第一准则,props应该像纯函数那样,只使用传递的属性,而不去修改它(想改也改不掉,改了就报错)。

为什么这么说,你想想,react本身就是单向数据流,父传递数据给子使用,如果在子组件内随意修改父传递的对象反过来影响了父,那这不就乱套了吗。

那么问题来了,如果父传了属性给子,子真的要改怎么办?也不是没办法,第一我们可以在父提供props同时,也提供一个修改props的方法过去给子调用,子虽然是调用点,但本质执行的是父的方法,这是可行的。第二点,将传递进来的props复制一份,自己想怎么玩就怎么玩,也不是不可以,比如:

  1. function Child(props) {
  2. // 拷贝一份自己玩
  3. let num = props.num;
  4. num++;
  5. return <div>{num}</div>;
  6. }
  7. class Parent extends React.Component {
  8. constructor(props) {
  9. super(props);
  10. this.state = { num: 1 };
  11. }
  12. render() {
  13. return (
  14. <div>
  15. <Child num={this.state.num} />
  16. </div>
  17. );
  18. }
  19. }
  20. ReactDOM.render(<Parent />, document.getElementById("root"));

当然如果子组件也是class组件也可以,还是这个例子,只是修改了Child部分:

  1. class Child extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. // 将传递进来的props赋予子组件的state
  5. this.state = {
  6. num:props.num
  7. }
  8. }
  9. render() {
  10. return <div>{++this.state.num}</div>;
  11. }
  12. }

再或者直接赋值成this上的一条属性:

  1. class Child extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. }
  5. // 将传递进来的props赋予给this
  6. num = this.props.num;
  7. render() {
  8. return <div>{++this.num}</div>;
  9. }
  10. }

以上便是复制一份的常规操作,我们再来看看父提供修改方法的做法:

  1. class Child extends React.Component {
  2. render() {
  3. return (
  4. <div>
  5. <div>{this.props.num}</div>
  6. <button type="button" onClick={() => this.props.onClick()}>
  7. 点我
  8. </button>
  9. </div>
  10. );
  11. }
  12. }
  13. class Parent extends React.Component {
  14. constructor(props) {
  15. super(props);
  16. this.state = { num: 1 };
  17. }
  18. // 传递给子组件使用的方法
  19. handleClick() {
  20. // 拷贝了state中num
  21. let num_ = this.state.num;
  22. // 自增
  23. num_ += 1;
  24. // 更新state中的num
  25. this.setState({ num: num_ });
  26. }
  27. render() {
  28. return (
  29. <div>
  30. <Child num={this.state.num} onClick={() => this.handleClick()} />
  31. </div>
  32. );
  33. }
  34. }
  35. ReactDOM.render(<Parent />, document.getElementById("root"));

我们来解释下这段代码,我们在Parent中定义了state,其中包含num变量,以及定义了handleClick方法,在

<Child num={this.state.num} onClick={() => this.handleClick()} />这行代码中,我们将state中的numhandleClick分别以numonClick这两个变量名传递进去了。

对于事件定义react有个规则,比如我们传递给子组件的变量名是on[Click],那么具体方法定义名一般以handle[click]来命名,简单点说,on[event]与handle[event]配对使用,event就是代表你事件具体含义的名字,有一个统一的规则,这样也利于同事之间的代码协作。

在子组件内部,我们通过props能访问到传递的num与onClick这两个属性,我们将其关联到dom中,当点击按钮就会执行父组件中的handleClick方法。有同学可能注意到handleClick中更新num的操作了,按照我们常规理解,直接this.state.num++不就行了吗,很遗憾,这是react需要注意的第二点,我们无法直接修改state,比如如下行为都不被允许:

  1. // 直接修改不允许
  2. this.state.num = 2;
  3. // 同理,这也是直接修改了state,也不被允许
  4. this.setState({ num: this.state.num++ });

官方推荐做法,同样也是将state中你要修改的部分拷贝出来,操作完成,再利用setState更新。

如果你了解过vue,在深入响应式原理一文中,也有类似的要求,比如请使用Vue.set(object, propertyName, value)去更新某个对象中的某条属性,而不是直接修改它,否则你的修改可能并不会触发视图更新,其实都是差不多的道理,这里就顺带一提了。

OK,到这里我们对于这一块知识点做个小结,props与state构成了react单向数据流的数据部分,同为属性,只是一个私有一个是从外部传递的而已。其次,props只读不可修改,若要修改请使用类似拷贝的折中方法,state除了拷贝外还得通过setState重新赋值。前面也说了,props就是外部传递的state,所以两者都不能直接修改也不是不无道理,记住这点就好了。

伍 ❀ 总

现在是凌晨1点半(封面图也是应景了),其实写到这里,第二部分知识我想说的也都差不多了,看了眼篇幅,四千多字,再长一些知识点可能也有点多了,所以这篇就先介绍到这里。怎么说呢,关于文章的编写我心里其实还是会有遗憾的,我毕竟只是个初学者,实战项目经验还远远不足,很多东西还不能从根源去解释清楚,比如setState可能是异步行为,所以不要用state变化作为你部分逻辑的执行判断条件,举个例子:

  1. class Parent extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. this.state = {
  5. bol: true,
  6. };
  7. }
  8. // 这是生命周期函数
  9. componentDidMount(){
  10. for (let i = 0; i < 5; i++) {
  11. if (this.state.bol) {
  12. console.log(i);
  13. this.setState({ bol: false });
  14. }
  15. }
  16. console.log(this.state.bol);//true
  17. }
  18. // 这也是生命周期函数
  19. componentDidUpdate(){
  20. console.log(this.state.bol);//false
  21. }
  22. render() {
  23. return null;
  24. }
  25. }
  26. ReactDOM.render(<Parent />, document.getElementById("root"));

我们预期的是在输出i为0之后,就修改bol状态,之后循环无法再次进入这段代码,但很遗憾,for循环会完整执行完毕并输出0-1-2-3-4,直到在生命周期componentDidUpdate中我们才捕获到修改成功的状态。遗憾的是我目前的经验还不足以将这块知识吃透,没吃透的东西我不会写,所以这里算留个坑吧,之后一定会单独写一篇文章介绍state的问题,把这块弄情况,那么这篇文章就先说到这里了,本文结束。

从零开始的react入门教程(二),从react组件说到props/state的联系与区别的更多相关文章

  1. React入门教程(二)

    前言 距离上次我写 React 入门教程已经快2个月了,年头年尾总是比较忙哈,在React 入门教程(一)我大概介绍了 React 的使用和一些注意事项,这次让我们来继续学习 React 一. Rea ...

  2. react 入门教程 阮一峰老师真的是榜样

    -  转自阮一峰老师博客 React 入门实例教程   作者: 阮一峰 日期: 2015年3月31日 现在最热门的前端框架,毫无疑问是 React . 上周,基于 React 的 React Nati ...

  3. React入门教程1---初见面

    React入门教程1---初见面:https://blog.csdn.net/solar_lan/article/details/82799248 React 教程 React 是一个用于构建用户界面 ...

  4. 无废话ExtJs 入门教程二十一[继承:Extend]

    无废话ExtJs 入门教程二十一[继承:Extend] extjs技术交流,欢迎加群(201926085) 在开发中,我们在使用视图组件时,经常要设置宽度,高度,标题等属性.而这些属性可以通过“继承” ...

  5. 无废话ExtJs 入门教程二十[数据交互:AJAX]

    无废话ExtJs 入门教程二十[数据交互:AJAX] extjs技术交流,欢迎加群(521711109) 1.代码如下: 1 <!DOCTYPE html PUBLIC "-//W3C ...

  6. 无废话ExtJs 入门教程二[Hello World]

    无废话ExtJs 入门教程二[Hello World] extjs技术交流,欢迎加群(201926085) 我们在学校里学习任何一门语言都是从"Hello World"开始,这里我 ...

  7. mongodb入门教程二

    title: mongodb入门教程二 date: 2016-04-07 10:33:02 tags: --- 上一篇文章说了mongodb最基本的东西,这边博文就在深入一点,说一下mongo的一些高 ...

  8. SpringBoot入门教程(二)CentOS部署SpringBoot项目从0到1

    在之前的博文<详解intellij idea搭建SpringBoot>介绍了idea搭建SpringBoot的详细过程, 并在<CentOS安装Tomcat>中介绍了Tomca ...

  9. PySide——Python图形化界面入门教程(二)

    PySide——Python图形化界面入门教程(二) ——交互Widget和布局容器 ——Interactive Widgets and Layout Containers 翻译自:http://py ...

  10. Elasticsearch入门教程(二):Elasticsearch核心概念

    原文:Elasticsearch入门教程(二):Elasticsearch核心概念 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:ht ...

随机推荐

  1. Java 使用二分查找快速定位元素位置

    转载请注明出处: 快速定位 一个有序数列中 某一个元素的位置: 共有三种思路: 第一种:使用 for 循环,匹配元素值 与循环变量的值是否相等,相等则循环变量时的 i 则为元素位置 第二种:使用 二分 ...

  2. Ubuntu解决Github无法访问的问题

    技术背景 由于IP设置的问题,有时候会出现Github无法访问的问题,经过一番的资料检索之后,发现如下的方案可以成功解决在Ubuntu下无法正常访问Github的问题(有时候可以打开,有时候又不行). ...

  3. 上下文中找不到org.springframework.boot.web.servlet.server.ServletWebServerFactory bean

    1.问题 报错如下: Description: Web application could not be started as there was no org.springframework.boo ...

  4. JavaScript 对象和 JSON 的区别

    参考原文:https://blog.csdn.net/jiaojiao772992/article/details/77871785/ 2.1 对象和 JSON 的区别 JSON 就是 JavaScr ...

  5. 他凌晨1:30给我开源的游戏加了UI|模拟龙生,挂机冒险

    一.前言 新年就要到了,祝大家新的一年: 龙行龘龘, 前程朤朤! 白泽花了点时间,用 800 行 Go 代码写了一个控制台的小游戏:<模拟龙生>,在游戏中你将模拟一条新生的巨龙,开始无尽的 ...

  6. 【C++】在搞touchgfx时遇见了一个初始化列表顺序与类中定义不一致的问题,error:when initialized here [-Werror=reorder]

    在搞touchgfx时遇见了一个初始化列表顺序与类中定义不一致的问题,error:when initialized here [-Werror=reorder] 初始化列表顺序与类中定义顺序不一致错误 ...

  7. JMS Controller生命周期

  8. [转帖]字符集 AL32UTF8 和 UTF8

    https://blog.51cto.com/comtv/383254# 文章标签职场休闲字符集 AL32UTF8 和 UTF8文章分类数据库阅读数1992 The difference betwee ...

  9. [转帖]linux中的set -e 与set -o pipefail

    https://www.cnblogs.com/xingmuxin/p/8431970.html 1.set -e "Exit immediately if a simple command ...

  10. [转帖]Kafka中offsets.retention.minutes和log.retention.minutes之间的区别

    https://www.cnblogs.com/lestatzhang/p/10771115.html 前言 在Kafka中,我们可能会发现两个与retention相关的配置: log.retenti ...