原文连接: learnreact.design/2018/01/15/…react
喜欢理由: 文笔生动 通俗易懂react-native
特别鸣谢: 原做者 Linton Ye 的倾情校对ionic
系列博客: 用通俗的语言和涂鸦来解释 React 术语post
在上篇文章中,咱们介绍了组件、props 和 state 。学习
props 和 state 的区别至关明显,肯定什么时候使用 props 和 state 彷佛也很简单。举个例子,屋顶的颜色天然就是 prop ,由于颜色是屋顶的固有属性。另外一方面,门的开关状态很显然是 state ,由于门在建立后还能够打开或关上。然而在本文中,咱们未来挑战这一思惟方式!动画
没开玩笑?!?没错,你所看到的东西既能够是 prop,又能够是 state 。并无绝对的界限。我将介绍一种更有用、偏实战的方式来思考 prop 和 state 。spa
当你读完本文后但愿你能从新回到这里,并可以轻松回答如下问题:翻译
你注意到房子周围的新成员了吗?试试点击房门!设计
查看由 focuser (@focuser) 在 CodePen 编写的 Demo : 有猫的 React 小屋。code
这是一只嗜睡的猫,门一关她就睡,只有当门再开启的时候才会起来。若是把门关上,她当即又睡过去了。
如今我来问你,若是实现猫的行为?先来试试吧!
先从下面的“代码”入手,花点时间先读一遍。(再次重申,这并不是真正的 JavaScript 代码,它只是以一种简化的形式来帮助你理解概念,同时不会被 JS 中的细节所干扰。)
House:
<div> <Roof /> <Wall /> <Window /> <Door /> <Cat /> </div>
Door:
State: status <!-- "open" 或 "closed" -->
<div>{state.status} door</div>
当点击门时
若是 door.state.status 为 "open"
将 door.state.status 修改为 "closed"
不然
将 door.state.status 修改为 "open"
复制代码
在 House
组件中又新增了 Cat
标签。那么 Cat
组件又是怎么样的呢?咱们来定义它。
猫要么睡、要么醒。这彷佛跟门的开关状态很相似。或许咱们一样可使用 state 来表示猫的状态:
Cat:
State: status <!-- "sleeping" 或 "awake" -->
<div>{state.status} cat</div>
复制代码
Cat
组件定义好后,还须要实现的就只剩下将猫和门的状态进行同步。门的状态为 “open” 时,咱们想要猫的状态为 “awake”,反之为 “sleeping” 。
就这么简单?看看再说吧…
既然咱们已经有了根据当前状态切换门状态的代码,莫不如咱们就在此处切换猫的状态:
Door:
State: status <!-- "open" 或 "closed" -->
<div>{state.status} door</div>
当点击门时
若是 state.status 为 "open"
将 state.status 修改为 "closed"
将 cat.state.status 修改为 "sleeping" <!-- 错误的 -->
不然
将 state.status 修改为 "open"
将 cat.state.status 修改为 "awake" <!-- 错误的 -->
复制代码
不幸的是,这不起做用!还记得组件的 state 是私有数据吗?只有在组件的内部才能访问。其余组件,不管是父组件仍是兄弟组件,都没法访问本组件的 state 。
很遗憾,咱们在 Door
组件内尝试修改猫的状态以失败了结。(转换成真正的 JavaScript 代码也不例外)
那么在 Cat
组件内来修改猫的状态如何?此次应该能够的,是吧?
Cat:
State: status <!-- "sleeping" 或 "awake" -->
<div>{state.status} cat</div>
当点击门时 <!-- 黑人门号脸???怎么个点击法? -->
若是 door.state.status 为 "open" <!-- 错误的 -->
将 cat.state.status 修改为 "sleeping"
不然
将 cat.state.status 修改为 "awake"
复制代码
毫无疑问,在 Cat
组件内修改猫的状态是没问题的。但咱们须要读取门的状态来决定猫的状态是什么。门的状态是 Door
组件的 state ,所以没法在 Cat
组件里访问!
呃!太蹩脚了。要保持门和猫的状态同步,咱们必需要在某处能同时访问二者。但看上去数据是经过设计而对外隐藏的。若是来解决此难题呢?
解决办法就是须要咱们灵活地理解 state 和 props 的用法。
House
组件:
House:
<div> ... <Door /> <Cat /> </div>
复制代码
Door
和 Cat
是并排放置的。或许这就是能够轻松同步它们的地方?
可是,咱们如今是在 House
组件内。与以前的尝试同理,在这里是没办法读取 Door
的 state 或者改变 Cat
的 state 。
但若是咱们使用 props 来替代 state 呢?
House:
<div>
...
<Door status="open" />
<Cat status="awake" />
</div>
复制代码
当门关上时:
House:
<div>
...
<Door status="closed" />
<Cat status="sleeping" />
</div>
复制代码
固然,门的状态不会是固定的值,它会随时间而改变。咱们用 doorStatus
来表示门的状态。
House:
<div>
...
<Door status={doorStatus} />
<Cat status={若是 doorStatus 为 'open' 值为 'awake' 不然为 'sleeping'} />
</div>
复制代码
这不就解决同步的问题了嘛。顺便问下,这个会变化的值 doorStatus
是什么?在组件中什么是能够改变的?没错,正是 state 。
House:
State: doorStatus <!-- 'open' 或 'closed' -->
<div>
...
<Door status={state.doorStatus} />
<Cat status={若是 state.doorStatus 为 'open' 值为 'awake' 不然为 'sleeping'} />
</div>
复制代码
太棒了!House
组件如今定义的很好,门和猫的状态也能完美同步。
咱们还须要修改 Door
和 Cat
组件,使用 props 来代替 state :
Door:
<div>{props.status} door</div>
Cat:
<div>{props.status} cat</div>
复制代码
正如你所见,由于咱们想要使用来父组件的 state,在这种状况下,为了设置猫的状态,门的状态实际上是来自于 House
的,咱们能够将相同的数据表示为父组件的 state,并将数据做为 props 传递给子组件。一般,这被称之为 state 提高。咱们将 state 移至组件的更高层级处。
如今门和猫的状态经过房子的 state 进行链接。若是想开门或唤醒猫的话,咱们须要更改 House
组件的 state 。
问题来了,哪里是惟一能够更新 House
的 state 的地方?就在 House
组件内,没错吧?
可是,咱们想要在 Door
里来触发此次更改。也就是说,咱们想要的效果是只有当点击门时才开门,而不是点击整个房子或窗户等。
因此 Door
组件须要作些改动:
Door:
<div>{props.status} door</div>
当点击门时
作某件事来修改 `House` 的 state
复制代码
但等等,以前不是说不能在 Door
组件内修改 House
的 state 吗?
没错。咱们没办法直接修改 House
的 state 。但并不等于说不能间接地修改。看下面…
在 House
组件内,咱们来写代码以实际修改它的 state :
House:
State: doorStatus <!-- 'open' 或 'closed' -->
toggleDoorStatus:
若是 state.doorStatus 为 'open'
将 state.doorStatus 修改为 'closed'
不然
将 state.doorStatus 修改为 'open'
...
复制代码
此刻,咱们还未指定什么时候运行这段代码。咱们只是给了它一个名字 (toggleDoorStatus
),以便稍后经过名称来找到它运行。
而后将 toggleDoorStatus
做为 prop 传递给 Door
组件:
House:
...
<div>
...
<Door ... onClickAction={toggleDoorStatus} />
...
</div>
复制代码
在 Door
组件中,咱们只需执行这个点击操做便可:
Door:
<div>{props.status} door</div>
当点击门时
执行 props.onClickAction <!-- 实际运行的是名为 "toggleDoorStatus" 的代码-->
复制代码
这就像把电视遥控器传递给其余人同样。某人在 Door
组件内按下了遥控器按键。House
组件里的电视机就会换台或加大音量。
将会发生什么取决于传给 Door
的遥控器是什么。它可能控制的是房间里的电视、空调或高保真音响系统。在 Door
组件内,某人须要作的只是按下遥控器的按键。
这就是咱们所须要的!下面是完整“代码”:
House:
State: doorStatus <!-- 'open' or 'closed' -->
toggleDoorStatus:
若是 state.doorStatus 为 'open'
将 state.doorStatus 修改为 'closed'
不然
将 state.doorStatus 修改为 'open'
<div>
...
<Door status={state.doorStatus} onClickAction={toggleDoorStatus} />
<Cat status={若是 state.doorStatus 为 'open' 值为 'awake' 不然为 'sleeping'} /> </div>
Door:
<div>{props.status} door</div>
当点击门时
执行 props.onClickAction
Cat:
<div>{props.status} cat</div>
复制代码
如今让咱们重温几个问题,props 和 state 的区别是什么?什么时候应该使用 state ?什么时候应该使用 props ?
若是你还记得的话,我曾说过 props 是组件的固有属性,它是不会改变的,而 state 是组件建立后才有的,它是能够改变的。当最初学习这两个概念时,它是有帮助的。
可是,咱们刚刚建立的示例让这一观点变得使人困惑。不管是门的开的,仍是猫是睡着的,这理所应当应该是 state ,但咱们却使用 props 来表示它们。这是为何?
事实证实,在 state 和 props 的选择问题上,仍是有很大的灵活性的。这取决于你看它的视角,你能够采用不一样的方式来为组件建模。例如,当门敞开之时,你能够说它是门的状态,你能够说它是房子的状态。
感到困惑了?这是一种更有用的思考问题的方式:
当应用运行时,若是 UI 须要变化,那它必定是 state 。当点击门时,门或开或关,那么它必定是某处的 state 。
可是,state 并不必定是更新组件的 state 。它可能位于某个上游组件中。这彻底都取决于咱们须要在何处以及如何使用这些信息。举个例子,咱们决定将门的状态从 Door
组件提高到 House
组件中,是由于咱们须要在 House
组件中使用它。
另外一方面,props 只是用来向下传递数据的东西。就像咱们以前将门的状态从 House
组件中向下传递给 Door
组件。
props 还能够用来向下传递控制。例如,咱们将事件处理方法从 House
传给 Door
。
并无,props 永远不会改变值。我懂你的意思,门开开关关,猫睡了又醒。由于咱们如今使用 props 来表示它们,很容易让人认为 props 的行为就比如 state,它们的值改变了,是这样吗?
这只是一个错觉,我发现它与翻页书的动画至关类似。
每次房子的 state 发生改变,旧的那只猫就会消失,而后一只崭新状态的猫将从新建立。可是这一过程发生的很是之快,这就形成了咱们的视觉残留,认为只有一只猫在那睡了又醒。
翻书中任何页面上的草图都不会移动。相似的,每只猫在其(短暂的)生命中始终保持清醒/沉睡。
好了,咱们经过一个更复杂的示例再次学习了 props 和 state 。在这个示例中,当点击门时,门须要切换开关状态,同时咱们还须要将门和猫的状态保持同步,
由于 state 是私有的,因此咱们须要将门的状态从 Door
组件提高到 House
组件中。这样咱们即可以在 House
组件中使用此数据来设置门和猫的状态。咱们将此数据做为 props 传给 Door
和 Cat
,以便它们根据门的状态来显示正确的图片。
另外一个需求是点击门时触发状态的变动。由于如今门的状态是 House
的 state,它是私有数据,只能在 Door
组件中间接来更改它。咱们在 House
组件中编写了实际修改 state 的代码,而后将其做为 props 传递给 Door
。这相似于把电视的遥控器传给别人。
本文中的示例或许会让你感到一丝困惑。下面是一种用来思考 prop 和 state 的更实用的方式:
到目前为止感受如何?若是你有任何问题或意见,请给我留言!