React:绘制思维模型
无论你使用 React 多年,还是刚刚入门,在我看来,拥有一个实用的思维模型是提升使用 React 信心的最快途径。
拥有良好的思维模型,你就能直观地理解复杂的问题和设备解决方案,这比你一步步寻找解决方案要快得多。
免责声明:本文由一位初学者撰写,旨在梳理他刚学到的新概念。您也可以在我的博客上阅读,我在那里解释了一些帮助我解决问题和应对复杂性的思维模型。
为什么心智模型很重要?
当我开始搭建网站时,我很难理解它的工作原理。用 WordPress 搭建博客网站很容易,但我对主机托管、服务器、DNS、证书等等一无所知。
随着我阅读文章并尝试各种方法,我开始掌握这个系统,了解它的工作原理,直到最终“恍然大悟”,我感觉能够自如地使用它。我的大脑已经围绕这个系统建立了一个心理模型,我可以用它来操作它。
如果有人能把他们的思维模型讲解给我听,我就能理解得更快。在这里,我将解释(并展示)我在 React 中使用的思维模型。这将帮助你更好地理解 React,并让你成为更优秀的开发者。
React 心智模型
React 帮助我们比以往更轻松地构建复杂的交互式 UI。它还鼓励我们以特定的方式编写代码,指导我们创建更易于导航和理解的应用程序。
看着 React 徽标时,头脑中的抽象模型
React 本身是一个心理模型,其核心是一个简单的想法:封装依赖于类似逻辑和 UI 的应用程序中的部分,React 将确保该部分始终保持最新。
无论你使用 React 多年,还是刚刚入门,清晰的思维模型都是让你充满自信地使用它的最佳途径。因此,为了将我的思维模型传递给你,我将从第一性原理开始,并在此基础上进行构建。
它的功能始终如一
让我们首先对 JavaScript 和 React 的基本构建块进行建模:函数。
React 组件只是一个函数
包含其他组件的组件是函数调用其他函数。Prop
是函数的参数。
这些都被 React 使用的标记语言 JSX 隐藏了起来。剥离 JSX,React 就是一堆互相调用的函数。JSX 本身就是一种实用的思维模型,它使 React 的使用更加简单直观。
让我们分别看一下每个部分。
组件是返回 JSX 的函数
React 与 JSX(JavaScript XML)结合使用,JSX 是一种能够以 HTML 形式编写代码,同时又能充分发挥 JavaScript 的强大功能的方法。JSX 提供了一种非常实用的思维模型,能够以直观的方式使用嵌套函数。
让我们先忽略类组件,专注于更常见的函数式组件。函数式组件是一个行为与其他 JavaScript 函数完全相同的函数。React 组件始终返回 JSX 代码,然后执行该代码并将其转换为 HTML。
简单的 JSX 如下所示:
const Li = props => <li {...props}>{props.children}</li>;
export const RickRoll = () => (
<div>
<div className='wrapper'>
<ul>
<Li color={'red'}>Never give you up</Li>
</ul>
</div>
</div>
);
通过 Babel 编译成纯 JavaScript:
const Li = props => React.createElement('li', props, props.children);
export const RickRoll = () =>
React.createElement(
'div',
null,
React.createElement(
'div',
{
className: 'wrapper',
},
React.createElement(
'ul',
null,
React.createElement(
Li,
{
color: 'red',
},
'Never give you up',
),
),
),
);
如果您发现此代码难以理解,那么您并不孤单,并且您会理解为什么 React 团队决定使用 JSX。
现在,请注意每个组件都是一个调用另一个函数的函数,并且每个新组件都是 React.createElement 函数的第三个参数。无论何时编写组件,记住它是一个普通的 JavaScript 函数都会很有用。
React 的一个重要特性是,一个组件可以有多个子组件,但只能有一个父组件。我之前一直对此感到困惑,直到我意识到这和 HTML 的逻辑是一样的:每个元素都必须位于其他元素内部,并且可以有多个子组件。你可以在上面的代码中注意到这一点,只有一个父 div 包含所有子组件。
组件的 props 与函数的参数相同
使用函数时,我们可以使用参数与该函数共享信息。在 React 组件中,我们将这些参数称为 props(有趣的是,我很久以前才意识到 props 是 properties 的缩写)。
在底层,props 的行为与函数参数完全相同,不同之处在于我们通过 JSX 更好的界面与它们交互,并且 React 为 props 提供了额外的功能,例如 children。
围绕函数创建思维模型
利用这些知识,让我们构建一个心理模型来直观地理解函数!
当我思考一个函数时,我会把它想象成一个盒子,每当它被调用时,它就会做一些事情。它可以返回值,也可以不返回:
function sum(a, b) {
return a + b;
}
console.log(sum(10, 20)); // 30
function logSum(a, b) {
console.log(a + b); // 30
}
由于组件是一种奇特的功能,因此它也使组件成为一个盒子,并以 props 作为盒子创建输出所需的成分。
当一个组件被执行时,它会运行其所有逻辑(如果有),并执行其 JSX 代码。所有标签都会转换为 HTML,所有组件都会被执行,这个过程不断重复,直到到达子组件链中的最后一个组件。
由于一个组件可以有多个子组件,但只有一个父组件,因此我将多个组件想象成一组盒子,一个盒子套着另一个盒子。每个盒子必须包含在一个更大的盒子中,并且里面可以包含许多更小的盒子。
但是,如果不理解一个盒子如何与其他盒子交互,那么代表一个组件的盒子的心理模型就是不完整的。
如何思考闭包
闭包是 JavaScript 的核心概念。它使 JavaScript 能够实现复杂的功能,理解闭包对于理解 React 至关重要。
它们也是新手最难掌握的功能之一,因此,我不会解释技术细节,而是展示我对闭包的心理模型。
闭包的基本描述是,它是一个函数。我把它想象成一个盒子,它阻止里面的东西溢出,同时允许外面的东西进入,就像一个半透性的盒子。但是溢出到哪里呢?
虽然闭包本身是一个盒子,但任何闭包都会位于更大的盒子内,最外层的盒子是 Window 对象。
一个描述 JavaScript 闭包的思维模型的框,显示窗口、脚本和 React 应用程序
窗口对象封装了其他所有内容
但是闭包是什么?
这closure
是 JavaScript 函数的一个特性。如果你使用函数,那么你就在使用闭包。
正如我之前提到的,函数是一个盒子,而闭包也是一个盒子。考虑到每个函数内部可以包含许多其他函数,闭包就是指函数能够使用其外部信息,同时又能防止其内部信息“溢出”或被外部函数使用的能力。
就我的思维模型而言:我将函数想象成一个个盒子套着一个盒子,每个小盒子都能看到外层盒子(或父盒子)的信息,但大盒子却看不到小盒子的信息。这就是我能给出的对闭包最简单、最准确的解释。
闭包很重要,因为它们可以被利用来创建一些强大的机制,而 React 充分利用了这一点。
React 中的闭包
每个 React 组件本身也是一个闭包。在组件内部,你只能将 props 从父组件向下传递给子组件,父组件无法看到子组件内部的内容。这个特性旨在简化应用的数据流追踪。为了找到数据的来源,我们通常需要沿着组件树向上查找是哪个父组件向下传递数据的。
React 中闭包的一个很好的例子是通过子组件更新父组件的状态。你可能做过这样的事情,却没有意识到自己在使用闭包。
首先,我们知道父组件无法直接访问子组件的信息,但子组件可以访问父组件的信息。因此,我们通过 props 将这些信息从父组件传递给子组件。在本例中,这些信息以函数的形式存在,用于更新父组件的状态。
const Parent = () => {
const [count, setCount] = useState(0);
return (
<div>
The count is {count}
<div>
<ChildButtons onClick={setCount} count={count} />
</div>
</div>
);
};
const ChildButtons = props => (
<div>
<button onClick={() => props.onClick(props.count + 1)}>
Increase count
</button>
<button onClick={() => props.onClick(props.count - 1)}>
Decrease count
</button>
</div>
);
当按钮发生 onClick 时,它将执行从 props props.onClick 接收的函数,并使用 props.count 更新值。
这里的要点在于我们通过子组件更新父组件状态的方式,在本例中就是 props.onClick 函数。之所以有效,是因为该函数是在 Parent 组件的作用域内(也就是其闭包内)声明的,因此它可以访问父组件的信息。一旦该函数在子组件中被调用,它仍然位于同一个闭包中。
这可能有点难以理解,所以我把它想象成闭包之间的“隧道”。每个闭包都有自己的作用域,但我们可以创建一个连接两者的单向通信隧道。
一旦我们理解了闭包如何影响我们的组件,我们就可以采取下一个重要步骤:React 状态。
将 React 的状态融入我们的思维模型
React 的理念很简单:它负责处理何时以及如何渲染元素,而开发者则负责控制渲染的内容。状态是我们决定渲染什么的工具。
当状态发生变化时,其组件会渲染,并重新执行其中的所有代码。我们这样做是为了向用户显示新的、更新的信息。
在我的思维模型中,状态就像盒子里的一个特殊属性。它独立于盒子内部发生的所有其他事情。它会在首次渲染时获得一个默认值,并始终保持最新值。
每个变量和函数在每次渲染时都会创建,这意味着它们的值也是全新的。即使变量的值从未改变,每次也会重新计算并重新赋值。状态则不然,它只有在通过事件请求更改时才会更改set state
。
状态遵循一个简单的规则:每当它发生变化时,它都会重新渲染组件及其子组件。属性遵循相同的逻辑,如果属性发生变化,组件将重新渲染。然而,我们可以通过修改它来控制状态,而属性则更加静态,通常会随着状态的变化而变化。
渲染思维模型:理解 React 的魔力
我认为渲染是 React 中最令人困惑的部分,因为渲染过程中发生的很多事情有时通过查看代码并不容易发现。因此,清晰的思维模型至关重要。
我设想的渲染方式是双重的:第一次渲染使盒子生效,也就是初始化状态。第二次渲染是重新渲染,盒子被回收利用,大部分内容都是全新的,但一些重要的元素,也就是状态,仍然保留。
每次渲染时,组件内部的所有内容都会被创建,包括变量和函数。这就是为什么我们可以使用变量来存储计算结果,因为它们每次渲染时都会重新计算。这也是为什么函数作为值不可靠的原因,因为它们的引用(函数本身的值)每次渲染时都会不同。
const Thumbnail = props => (
<div>
{props.withIcon && <AmazingIcon />}
<img src={props.imgUrl} alt={props.alt} />
</div>
);
上述代码会根据组件接收的 props 给出不同的结果。React 必须在每次 props 更改时重新渲染,是因为它希望让用户及时了解最新信息。
但是,状态在重新渲染时不会改变,其值保持不变。这就是为什么盒子会被“回收”,而不是每次都创建一个全新的盒子。React 内部会跟踪每个盒子,并确保其状态始终一致。这就是 React 知道何时更新组件的方式。
通过想象一个盒子被回收,我就能理解它里面发生了什么。对于简单的组件来说,这很容易理解,但组件越复杂,接收的 props 就越多,维护的状态就越多,清晰的思维模型就越有用。
完整的 React 思维模型:将所有内容整合在一起。
现在我已经分别解释了这个谜题的各个部分,让我们把它们放在一起。这是我对 React 组件的完整思维模型,直接将我的想象转化为文字。
我将 React 组件想象成一个盒子,其内部包含所有信息,包括其子组件(更多的是盒子)。
就像现实世界中的盒子一样,它里面可以包含其他盒子,而这些盒子又可以包含更多的盒子。因此,每个盒子/组件必须有一个父级,而一个父级可以有多个子级。
这些盒子是半透性的,这意味着它们不会向外泄漏任何东西,但可以像使用自己的信息一样使用来自外部的信息。我设想用这种方式来表示 JavaScript 中闭包的工作原理。
在 React 中,组件之间共享信息的方式称为 props,同样的想法适用于函数,然后它被称为参数,它们以相同的方式工作,但使用不同的语法。
在组件内部,信息只能从父组件向下传递到子组件。换句话说,子组件可以访问父组件的数据和状态,但反之则不行。我们共享这些信息的方式是通过 props。
我把这种定向的信息共享想象成一个个盒子套着一个盒子。最里面的盒子可以吸收父级的数据。
React 组件间数据共享的思维模型,可视化为信息向下流动,即数据从父组件共享到子组件。
不过,首先必须创建盒子,这发生在渲染时,此时会为 state 赋默认值,并且像函数一样,组件内的所有代码都会被执行。在我的思维模型中,这相当于创建了盒子。
后续渲染(或重新渲染)会再次执行组件中的所有代码,重新计算变量、重新创建函数等等。除了状态之外,所有内容在每次渲染时都是全新的。状态的值在渲染过程中保持不变,并且仅通过 set 方法进行更新。
在我的心理模型中,我将重新渲染视为回收盒子,因为其中大部分都是重新创建的,但由于 React 跟踪组件的状态,它仍然是同一个盒子。
当一个盒子被回收时,它里面的所有盒子(也就是它的子盒子)也会被回收。这可能是因为组件的状态被修改了,或者 props 发生了变化。
请记住,状态或属性的改变意味着用户看到的信息已经过时,而 React 始终希望保持 UI 更新,因此它会重新渲染必须显示新数据的组件。
结论
通过运用这些思维模型,我使用 React 时更加自信。它们帮助我将原本错综复杂的代码转化为清晰的思维导图。它还揭开了 React 的神秘面纱,让我能够更加轻松地使用它。
一旦你开始理解 React 背后的核心原理并创建一些方法来想象你的代码如何工作,React 就不会那么复杂。
希望这篇文章对你有所帮助,阅读和写作都一样愉快!我意识到我对 React 的理解是直观的,而将这种理解转化为文字却颇具挑战性。
文章来源:https://dev.to/carter/react-painting-a-mental-model-1hd8