React 简介,第 2 部分
已发表: 2020-07-16欢迎回到我们对 React 的介绍,在这里我们会教您基础知识,以便您了解这项技术的工作原理,从而编写更好的代码并创建更好的用户界面。 在上一篇文章中,我们讨论了如何将 React 组件创建为简单的纯函数。 我一遍又一遍地重复的口头禅是 «React 组件是一个简单的函数,它获取一组属性 ( props ) 并生成一些 HTML»。
上次我们讨论 React 时,我们以以下问题结束:如果一个组件是一个如此简单的函数,我们如何用它做任何有用的事情? 好了,今天我们来聊聊 React 的动态部分:也就是我们如何让组件对用户事件做出反应。
设置环境
本教程的第一部分有一些理论,但没有包含任何示例,对此我深表歉意; 我认为最好的学习方法是做,所以让我们解决这个问题! 从现在开始,我将教给您的所有内容都将基于我们将要处理的示例,所以让我们设置我们将首先使用的环境。
我假设您有一个可以使用的 WordPress 站点,以及开发工具node和npm 。 如果不是这样,在这篇文章中,我解释了如何使用 Lando 创建本地 WordPress 安装,并且npm的文档包含安装node和npm所需的一切。
首先,让我们创建一个简单的 WordPress 插件,我们将在其中拥有我们的 React 组件。 为此,请在wp-content/plugins中创建一个名为react-example的文件夹,并将react-example.php文件放入其中,其中包含以下内容:
<?php /** * Plugin Name: React Example * Description: Example. * Version: 1.0.0 */ namespace React_Example; defined( 'ABSPATH' ) or die(); function add_page() { add_menu_page( 'React Example', 'React Example', 'read', 'react-example', function () { echo '<div></div>'; } ); } add_action( 'admin_menu', __NAMESPACE__ . '\add_page' ); function enqueue_scripts() { $screen = get_current_screen(); if ( 'toplevel_page_react-example' !== $screen->id ) { return; } $dir = untrailingslashit( plugin_dir_path( __FILE__ ) ); $url = untrailingslashit( plugin_dir_url( __FILE__ ) ); if ( ! file_exists( "{$dir}/build/index.asset.php" ) ) { return; } $asset = include "{$dir}/build/index.asset.php"; wp_enqueue_script( 'react-example', "{$url}/build/index.js", $asset['dependencies'], $asset['version'], true ); } add_action( 'admin_enqueue_scripts', __NAMESPACE__ . '\enqueue_scripts' );该插件只是在您的 WordPress 仪表板上添加一个名为React Example的新页面,并在其中加载一个 JavaScript 文件。 现在,结果应该是一个空白页:

这不应该让人感到意外,因为我们还没有在 JavaScript 中实现任何东西。
正如我们在上一篇关于如何向 Gutenberg 添加按钮的文章中看到的那样,您必须在终端中运行以下命令(在我们正在创建的插件文件夹中):
npm init npm install --save-dev @wordpress/scripts 如果您希望能够编写 React 组件并转译 JavaScript,只需按照屏幕上的说明进行操作。 哦,别忘了更新你的package.json文件,让它包含@wordpress/scripts scripts的所有相关脚本。
最后,使用以下index.js文件创建一个src文件夹:
// Import dependencies import { render } from '@wordpress/element'; // Create component const HelloWorld = () => <div>Hello World</div>; // Render component in DOM const wrapper = document.getElementById( 'react-example-wrapper' ); render( <HelloWorld />, wrapper ); 运行npm run build (或npm run start如果您希望在每次更改源文件时自动转换代码),刷新页面,瞧,一切准备就绪:

在 React 中创建响应式功能组件
现在让我们创建一个对用户事件做出反应的组件。 我希望我们一起实现的例子是这个:

一个简单的计数器,允许您分别使用+和-按钮将其值递增或递减一。 我知道它非常简单,但我希望它能帮助您理解“数据模型”和“用户界面”之间的区别,并指导您完成动态组件的实现。
更好的代码组织
让我们从改进代码的组织开始。 今天的示例非常简单,您可能想跳过这一步,但我认为从一开始就组织起来很有帮助。
在src中,创建一个新文件夹components ,其中将包含我们应用程序使用的所有组件。 由于今天的示例只有一个组件Counter ,我们需要做的就是创建一个文件counter.js :
export function Counter() => ( <p>Counter</p> ); 如您所见,它只是简单地导出了负责渲染我们组件的函数。 然后,修改src/index.js ,让它不渲染我们的示例HelloWorld ,而是导入并使用我们的新Counter组件:
// Import dependencies import { render } from '@wordpress/element'; import { Counter } from './components/counter'; // Render component in DOM const wrapper = document.getElementById( 'react-example-wrapper' ); render( <Counter />, wrapper );就是这样!
实现计数器组件
今天的目标是实现一个跟踪计数器值的组件,我们可以通过单击几个按钮来增加或减少计数器的值。 这意味着我们的组件必须具备三样东西:
- 显示当前值的元素
- A +按钮增加所述值
- A -按钮减少它
实现这个组件非常简单:
export const Counter = ( { value } ) => ( <div> <div>Counter: <strong>{ value }</strong></div> <button>+</button> <button>-</button> </div> ); 因为您知道您的组件将一个value作为其道具之一,并且必须包含两个按钮。 不幸的是,如果您希望这些按钮修改值,您可能不知道下一步该做什么,特别是如果我们考虑到这一点,上次我们谈到这一点时,我告诉过您“组件无法更改其属性”。

作为道具的功能与责任分离
请记住:React 组件是一个接收属性并生成 HTML 的函数。 这意味着,无论谁调用这个函数(或者换句话说,谁使用这个组件)都必须给它它需要的所有参数(属性)。
您的直觉已经告诉您,计数器的值是它需要的属性之一。 我们不知道这个值是从哪里来的(我们也不关心),但我们知道我们的组件会被赋予这个值。 你也知道我们的Counter组件必须能够以某种方式修改这个值。 换句话说,组件应该是这样的,当用户点击+或-时,值应该被更新。
如果您考虑一下这个语句,您会很快意识到Counter组件不仅需要一个value (类型为 number ),而且它还需要两个额外的函数类型的道具:一个增加所述值,另一个减少它的一个。 换句话说, Counter需要三个不同的props :
-
value:它是一个数字和计数器的值(duh!) -
onIncrease:它是一个函数,在调用时会增加计数器的值 onDecrease:这是另一个函数,在调用时会减少计数器的值
如果我们假设我们的组件将被赋予这三个 props,那么组件本身就变得非常简单:
export const Counter = ( { value, onIncrease, onDecrease } ) => ( <div> <div>Counter: <strong>{ value }</strong></div> <button onClick={ onIncrease }>+</button> <button onClick={ onDecrease }>-</button> </div> ); 注意我们的组件没有执行它接收到的函数(正如我们已经说过的,这是“禁止的”,因为它会触发副作用)。 它所做的只是将它们连接到我们按钮的click事件。 这意味着我们已经成功地实现了一个纯函数(所有组件都应该如此),因为给定相同的三个道具,结果将始终是具有相同“连接”的相同 HTML。
不幸的是,如果您尝试使用该组件,您会发现没有任何效果
为组件提供它需要的所有道具
难怪它不起作用! 当我们使用这个组件时,我们没有遵守它的合同,也没有添加它正常工作所需的道具。 看看index.js :
render( <Counter />, wrapper ); 计数器没有value ,也没有onIncrease或onDecrease函数!
好吧,让我们快速解决这个问题。 我们所要做的就是创建一个变量来存储计数器的值并实现一个更新它的函数,以便我们可以在我们的组件中使用它们:
// Import dependencies import { render } from '@wordpress/element'; import { Counter } from './components/counter'; // Store value let value = 0; function setValue( newValue ) { value = newValue; } // Render component in DOM const wrapper = document.getElementById( 'react-example-wrapper' ); render( <Counter value={ value } onIncrease={ () => setValue( value + 1 ) } onDecrease={ () => setValue( value - 1 ) } />, wrapper );由于这种架构,我们设法将用户界面(即组件)与数据管理完全分开。 该组件完全不知道谁或如何存储值; 它所关心的是它得到它需要的道具。
如果再次刷新页面,您会看到现在计数器确实显示了默认计数器值(即0 ),但是单击+和-还不起作用……
在更新时重新渲染组件
如果您查看前面的代码片段,您可能会认为现在应该可以正常工作了。 但显然他们没有……那有什么问题? 好吧,让我们进行一个小测试。 修改setValue函数,以便除了将新值分配给value之外,还将其记录在控制台中:
function setValue( newValue ) { value = newValue; console.log( 'Value is', value ); }并再次检查事情是否按预期工作。 只需打开浏览器的开发人员工具并单击您的按钮:

很有趣,对吧? 看起来每次用户单击按钮时都会正确更新value ,但组件永远不会更新。
如果要渲染组件,则需要使用所需的道具调用其纯函数。 当某个 prop 发生变化时,需要用新的值再次调用纯函数,这样才能生成新的 HTML。 所以,如果我们希望我们的 UI 显示最新的value ,我们需要在每次更新value时手动重新渲染组件:
// Import dependencies import { render } from '@wordpress/element'; import { Counter } from './components/counter'; // Store value let value = 0; function setValue( newValue ) { value = newValue; refreshComponent(); } // Render component in DOM const wrapper = document.getElementById( 'react-example-wrapper' ); function refreshComponent() { render( <Counter value={ value } onIncrease={ () => setValue( value + 1 ) } onDecrease={ () => setValue( value - 1 ) } />, wrapper ); } refreshComponent(); 如您所见,我们只是创建了一个名为refreshComponent的新函数,用于在第一次和每次通过setValue更新value时渲染组件。 这会导致预期的行为:

真的吗? 渲染组件看起来像垃圾!
如果你按照今天的教程走了这么远,你可能对我们重新渲染 React 组件的方式有点失望。 我不怪你——本教程的最后一部分确实很糟糕。 但那是因为它只是一个简单的示例,向您展示如何将“数据”与“用户界面”分开。
今天你学到了两个非常重要的教训:
- 组件可能需要的 props 不仅是“数据”(例如数字、字符串、数组、对象……),还可以是“函数”。 这意味着我们可以将这些函数连接到 DOM 事件,以便它们在用户执行某些操作时“自动”运行,从而更新我们应用程序的状态。
- 我们使用的组件(UI)和数据是完全独立的。 React 组件并不关心它需要的 props 由谁或如何管理。 这意味着我们可以实施一个糟糕的解决方案来管理和更新我们的计数器的
value,并且一切都会“正常工作”。
在下一篇文章中,我们将改进此解决方案并使用 WordPress 商店(基于 Redux)来管理应用程序状态。 别错过!
爱马仕里维拉在 Unsplash 上的特色图片。
