资讯专栏INFORMATION COLUMN

[ 一起学React系列 -- 2 ] UI的灵魂--State

XBaron / 699人阅读

摘要:首先卖个关子,下面我们一起来复习下小学还是初中的一枚数学知识。一旦更改了,会触发组件的重新渲染。为了页面渲染性能的考虑,有助于在中进行比较并确定是否重新渲染。

概念引入
对于React来说, 没有State就没有页面的渲染, 我们也将什么都看不到

咋一听怎么那么唬人?不过的确是这样,正如标题所言State是UI的灵魂。我们都知道React的核心思想之一是组件化,将页面所展示的东西按一定的规则分割成很多份并进行一一封装最后抽象成我们现在所称为的"组件", 就好像我们搭积木一样,一个城堡就是通过一个个小方块堆叠在一起的。但是FaceBook觉得如果仅仅是简单的封装那么和普通的moudle有什么区别?和咸鱼又有什么区别?于是FaceBook给这些"组件"赋予了灵魂(之一) -- State
什么叫State?顾名思义就是状态的意思。每个组件都有自己的状态,比如开关的闭合、颜色的切换和显示与隐藏等等都会涉及到状态的切换。首先卖个关子,下面我们一起来复习下小学(还是初中?)的一枚数学知识。

y=f(x)  -->(假如这是一个一元一次函数)

Are you kidding me?这是要侮辱在座的智商?不不不,请放下手里40米的大刀,笔者想抛个砖引个玉。
这是再简单不过的了,它表示y是关于x的函数。函数在数学中是十分的严谨,x与y是一一对应关系换句话说就是同一个x代入运算得到的永远是同一个y;同一个y代入运算得到的永远是同一个x,这个特性很像Redux中的Reducer,本质上是一个纯函数

初识State

那么如果我们把这个公式带到React中会有什么样的化学反应呢?

UI=f(State)

有木有感觉眼前一亮,Excuse me?竟然把State和UI通过一个公式关联起来?其实本质上就是这么简单。
同时,我们还可以继续用函数的思想去思考它:

输入特定的State只能输出特定的UI

根据特定的UI就能反推出相应的State

当然实际结果也是如此,React组件所渲染出来的东西与State有直接而且唯一的关系,换句话说就是State决定组件显示什么而且只有State才能决定组件显示什么

比如上面提到的一个组件可能有很多切换的动作,开关、颜色、显示与消失等等...本来这种切换的动作需要我们自己通过操作DOM来实现,但是FaceBook在设计React之初就把 直接操作DOM 这条路给堵死了(但仍给我们提供的必要的接口已备不时之需,后续文章会有相应内容),究竟为什么要这么做?因为我们都知道前端开发中特别消耗性能的是DOM操作,一旦处理不当就会影响整个页面的展示效果,因此FaceBook一不做二不休直接搞出了一个State出来,让开发者去输入State随后React自己去操作相应的DOM。

这么做有两个好处

使得State成为页面的唯一数据来源和页面元素变换的唯一依据。

提高页面的渲染性能(当然这不是React高效渲染的决定性因素)。

State大大提高了开发者对React组件的开发效率而不用担心页面性能问题,可谓是一举多得。

实例展示:静态State

下面我们来开发一个简单的文字展示组件:

import React, {Component} from "react"       ---line 1

class Show extends Component {               ---line 2

    constructor(props) {                     ---line 3
        super(props);
        this.state = {                       ---line 4
            content: "Hello World"
        }
        //this.propName = propValue;         ---line 5
    }

    render() {                               ---line 6
        return (
            

{this.state.content}

---line 7 ) } } export default Show;
首先一起来分析下这段代码:
line 1: 日常导包
line 2: ES6创建对象的方法。强烈推荐这么写
line 3: 该组件的构造方法,如果组件有属性默认值那么就需要写构造函数
line 4: 这里表示该组件有自己的State属性而且它还是一个字面量对象,所以与该组件有关的所有State都应该写在这个字面量对象中。从代码中看出该组件有一个State对象content,它包含着这个组件需要展示的一段文字。
line 5: 如果我们想给这个组件定义State以外的属性,那么就可以项这行所写一样,不过需要放在this对象中,这样才能在组件中通过this对象读取到。
line 6: render方法是最终构建组件结构的地方,因为组件究竟长什么样子,需要在这里写。
line 7: 正如这个组件需要做的事情,我们在render方法中返回这个p标签用来显示文字信息。因为所需要的文字信息保存在State对象中,State又保存在this对象中,所以如何去获取文字信息在这里不需要过多赘述。另外需要注意的是,render方法return的节点只能是一个,不能是多个。如果你的组件结构复杂,请在最外层用div这样的标签包一下然后再返回

下面看具体效果:


是不是很简单?

实例展示:动态State

接下来再说一下状态变化,因为状态就是用来更改的,如果不更改那和咸鱼有什么区别?

先思考下:React状态应该如何更改?

这个问题笔者第一次遇到的时候第一反应就是:直接改啊!!!(然后被piapiapia打脸),先试下吧:

import React, {Component} from "react"

class Show extends Component {

    constructor(props) {
        super(props);
        this.state = {
            content: "Hello World"
        }
    }

    changeState = () => {
        this.state.content = "I"m React State";
    };

    render() {
        return (
            

{this.state.content}

) } } export default Show;

我们创建一个函数用来更改响应的State,然后实际运行的时候发现不管怎么点按钮都毫无作用?Why?

因为React给我们提供了专门用于更改状态的方法, this.setState()

我们来重新试一下:

import React, {Component} from "react"

class Show extends Component {

    constructor(props) {
        super(props);
        this.state = {
            content: "Hello World"
        }
    }

    changeState = () => {
        this.setState({
            content: "I"m React State"
        })
    };

    render() {
        return (
            

{this.state.content}

) } } export default Show;
这个方法需要我们传入一个字符量对象,key是我们需要更改的那个State,这里是content; value是我们所期望要更改的值。( [] 是转移符)。

可以看出页面中那行字变成了我们所期望的文字。所以正如我们所说:

更改State要使用this.setState()方法。

一旦更改了State,会触发组件的重新渲染。实际上是运行一次组件中的render方法

异步的setState

这个点笔者想不出合适的引入点,所以就直接抛出来了。这个问题很有趣,因为巧合的是笔者的一个萌妹子同事在学习React时候恰好遇到这个问题,代码大概是这样:

onchange = () => {
    this.setState({
        name: "Demo"
    });

    this.props.change(this.state.name)//调用父组件通过props传过来的方法
};

她的本意是在本组件更改了name这个State后再通过调用父组件的方法实现父组件name的重新渲染。我们看出this.props.change(this.state.name)传入的参数是直接从State中取的,但是实际运行的时候却不是如想象中那样同时更改,现象是第一次点击时候本组件成功渲染,但是父组件并没有同时渲染;第二次点击时候父组件才渲染成对应的名字。
为什么呢?
因为this.setState这个方法不是同步的而是异步的,了解JavaScript中Event Loop机制的朋友都知道,如果一段js代码中有异步的代码那么会将其放在一个队列中,等待这段代码其余代码运行完后再从那个队列中取出异步代码运行。this.setState机制也和它差不多,当我们set一个State后React并不会立即去更改对应的State,而是在合适的时机下进行更改甚至为了提高性能会将多个setState过程合并成一个。为了证明这个异步机制,我们通过打印的方式做个试验:

import React, {Component} from "react"

class Show extends Component {

    constructor(props) {
        super(props);
        this.state = {
            content: "Hello World"
        }
    }

    changeState = () => {
        console.log(`1 -- ${this.state.content}`);
        this.setState({
            content: "I"m React State"
        });
        console.log(`2 -- ${this.state.content}`);
        console.log("end");
    };

    render() {
        return (
            

{this.state.content}

) } } export default Show;

控制台打印结果如下:

可以看出,页面正常渲染了说明对应的State已经更改了,但是控制台显示的信息却没有更改后的现象,所以可以确定真正的setState并不是调用this.setState()方法的瞬间,而是在之后的某个时间。所以有个问题需要注意:不要用当前的State去计算下一个State,因为你不能保证当前的State是最新的

但如果有个需求,需要在更改State后立即执行某个动作怎么办?

正常来说我们无法预知真正的setState是在何时,所以React理所当然得给我们提供了办法,那么就是this.setState的第二个参数,第二个参数是一个方法,当对应的State修改成功后会立即执行,我们修改下代码:

...
changeState = () => {
    console.log(`1 -- ${this.state.content}`);
    this.setState({
        content: "I"m React State"
    }, () => {
        console.log(`3 -- ${this.state.content}`);
    });
    console.log(`2 -- ${this.state.content}`);
    console.log("end");
};
...

看结果咯:

与预期一致,没毛病!!!

State的不可变

看到这个小标题,估计很多人会很懵逼,前面还说不更改的State和咸鱼有什么区别怎么到这里就要不可变了?其实是混淆了。
官方的建议是将State的所有对象当做是不可变对象,一旦每个对象更改了那么需要重新创建这个对象。举例子说,前面的代码中有:

this.state = {
    content: "Hello World"
}

当我们更改了content的值,用"I"m React State"替换了原有的"Hello World"。其实在这里,content对用的value不仅仅是内容上的变化也是地址上的变化,这种在基本变量上体现不出来,比如我们有个State要保存一个列表内容那么就得是个数组(字面量对象亦如此):

this.state = {
    navis: ["React","Vue","Angular"]
}

这个时候如果我们直接将navis的值拿出来push一个元素进去然后setState:

addNavi = () => {
    this.setState({
        navis: this.state.navis.push("React-Native")
    })
};

结果是页面并没有重新渲染,Why? 因为React在对比navis新的和老的两个值时候发现它们的地址都没变化就认为它们内容也没变化就不会重新渲染。这是个坑!!!。所以此时State对象的不可变原则就有作用了,解决方案有两个:

1、 复制原来的值,push完后进行setState。
addNavi = () => {
    let navisCopy = this.state.navis.slice();
    this.setState({
        navis: navisCopy.push("React-Native")
    })
};

这样就能正常运行了,因为navis对应的值不仅仅在内容上变了,地址也变化了,React检测到变化后就进行了重新渲染。

2、第三方插件

Immutable.js

immutability-helper

immutability-helper-x

至于为什么需要这么做?

State数据更明确,方便管理和开发调试。

为了页面渲染性能的考虑,有助于在shouldComponentUpdate中进行比较并确定是否重新渲染。

Bingo...本期的博文就结束了,这期笔者也精心准备了很久,希望大家都能喜欢!!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/97365.html

相关文章

  • [ 一起React系列 -- 3 ] UI扩展数据源Props以及Props约束

    摘要:所以还是印证那句话是组件渲染的唯一依据。所以对组件的进行约束是创建一个健康组件的必要条件。这里我们约束属性类型为。使用方式运行结果没有错误假如我们再加入一个子组件控制台如预期报错自定义约束万物皆有其局限性。 日常扯淡前的废话 上一篇我们介绍了React中State对象,说到它是组件渲染的唯一依据;当然我们也可以认为State是组件中的数据源之一,它保存着组件渲染的所有数据并且可以直接作...

    kumfo 评论0 收藏0
  • [ 一起React系列 -- 6 ] 秘术之时间旅行-1

    摘要:所谓的时间旅行从广义上来说无非就是三个动作回到过去进入未来回到现在,这个无论是从现实还是前端技术来说都是可靠的。单从技术栈来说,时间旅行不是一门技术而是一个思想套路。 标题看起来挺新颖的,笔者都觉得很高大上是不是哈哈... 抛转 时间旅行在生活中是一个非常吸引人的概念,虽然现在无法实现但说不定未来的某天就实现了!然后就穿梭会过去杀掉小时候的自己然后就开始懵逼自己是谁类似的狗血剧情......

    付伦 评论0 收藏0
  • [ 一起React系列 -- 4 ] 透传Context

    摘要:官方对的介绍是意思就是提供了一种通过组件树传递数据的方法,而无需在每个级别手动传递。这也是基于重要物证哈哈实例使用学习技术最终是要有产出的。依然被视作一个组件,不过不同的是它的子组件必须是一个方法并且该方法接收当前对象并最终返回一个节点。 抛转引玉 通过上一篇的科普我们知道如果父节点需要向子节点传递数据,那么就得通过Props来实现;那么摆在我们眼前的就有一个问题了:现有N个节点并且它...

    firim 评论0 收藏0
  • [ 一起React系列 -- 7 ] 秘术之时间旅行-2

    摘要:但这样做的缺点很多,不利于状态在组件之间共享。所以本篇使用作为状态管理器来实现时间旅行。并且从中可以看出过程不仅仅向对象中添加一个状态对象,还对进行了加一操作,这是为了保证状态与保持同步。 距离上一次更新已经有半个月了,这半个月来主要在忙两件事:一个是最近老板给了个自动化测试任务,另一个是和学校的弟弟一起搞一个微信小游戏...emmmm!其实主要是懒!!! 本篇是作为上篇的续集,不知道...

    nidaye 评论0 收藏0
  • React习之初入React世界

    摘要:语法将语法直接加入到代码中,再通过翻译器装换到纯后由浏览器执行。事实上,并不需要花精力学习。可以说,基本语法基本被囊括了,但也有少许不同。明确的数据流动。这条原则让组件之间的关系变得简单且可预测。使用获取和显示回调。 JSX语法 JSX将HTML语法直接加入到JavaScript代码中,再通过翻译器装换到纯JavaScript后由浏览器执行。在实际开发中,JSX在产品打包阶段都已经编...

    cjie 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<