Skip to content

React hooks:它不是魔法,是数组 #10

Description

@penouc

原文:https://medium.com/@ryardley/react-hooks-not-magic-just-arrays-cd4f1857236e

React hooks 它不是魔法,是数组

在本文中我将会使用一些图表来给大家解构一下这个新提案

首先要表明的是我是 hooks API 的粉丝。在使用 hooks 的时候会有一些奇怪的约束。现在我将给还在疑惑为什么有这些奇怪的约束的大家展示一个模型 — 应该如何使用这个新的 API 。

警告 Hooks 是一个实验性的提案
这篇文章所介绍的 hooks API 现在还是一个实验性的提案。你能在这个地址找到稳定版本的  React API 


hooks 该如何使用

很多人都在为新的 hooks API 的草案的『魔法』感到困惑,所以我现在想着尽自己所能尝试着解读一下这个新语法。

hooks 的规则

React 核心团队对 hooks 的使用约束主要有两个,你可以从这个文档上面查看到 hooks proposal

  • 不要在循环语句,条件语句,以及内层嵌套函数中调用 Hooks
  • 只能通过 React 的方法发起调用 Hooks

第二条是不言自喻的。为了将一个行为绑定到函数式组件上,你需要将这个行为关联到一个组件上了。
使用这样的一个 API 看上去非常的不自然,这也导致我过去一度困惑不已。所以今天我决定探索一番它。

hooks 中所有的状态管理都是与数组有关的

为了更好的建立一个模型,我们先来看一个 hooks 的简单实现。

请注意这是推测,只是一种可能实现 hooks API 可能的方式以用来帮助您来理解它。 这不一定是 API 在内部的工作方式。 这只是一个提案,未来这个可能发生变化。

怎么实现 ‘useState()’ ?

下面的例子将会展示状态 hook 是如何工作的。

首先我们可以从以下的组件开始:

function RenderFunctionComponent( {
	const [firstName, setFirstName] = useState['Rudi'];
	const [lastName, setLastName] = useState['Yardley'];

	return (
		<Button onClick={() => setFirstName('Fred')} >Fred</Button>
	)
}

hooks API 背后的意图是你能够使用一个 setter 函数,这个函数是 hook 方法返回数组的第二个对象,并且这个 setter 方法将会通过 hook 管理这个状态(即数组的第一个对象)。

所以 React 准备用这个来做什么呢?

让我们理解一下它在 React 内部如何工作。存储的状态可在执行上下文中用于呈现特定组件。 这意味着存储的数据位于正在呈现的组件之外。 此状态不与其他组件共享,它被维持在一个作用域中,这个作用域属于随后渲染的那个特定组件。

  1. 初始化
    创建两个空数组: ‘setters’ 和 ‘state’
    把游标设到 0
    初始化:两个空数组,游标为 0

  2. 第一次渲染
    第一次执行这个组件函数
    每次都会调用 useState() 方法, 当第一次执行的,推一个 setter 函数(绑定游标位置)到 setters 数组中,并且推一些状态到 state数组中。
    第一次渲染:对象被写进数组中,并且游标数自增

  3. 后续渲染
    每次后续渲染都会重置光标,并且只从每个数组中读取这些值。

  4. 事件处理
    每个 setter 都有一个对它的光标位置的引用,因此触发对任何 setter 函数的调用,它都将改变状态数组中该位置的状态值。
    setters 函数会记录它们的索引,并且能通过索引修改值

给大家展示一个简单的实现

以下是代码

let state = [];
let setters = [];
let firstRun = true;
let cursor = 0;

function createSetter(cursor) {
	return function setterWithCursor(newVal) {
 		state[cursor] = newVal;	
	};
}


// This is the pseudocde for the useState helper
// 下面是 useState 实现的伪代码

export function userState(initVal) {
	if (firstRun) {
		state.push(initVal);
		setters.push(createSetter(cursor));
		firstRun = false;
	}

	const setter = setters[cursor];
	const value = state[cursor];

	cursor++;
	return [value, setter]
}

// 使用这个 hooks 的组件
function RenderFunctionComponent() {
	const [firstName, setFirstName] = useState('Rudi'); //cursor = 0
	const [lastName, setLastName] = useState('Yardley'); //cursor = 1
	
	return (
		<div>
			<Button onClick={() => setFirstName('Richard')}>Richard</Button>
			<Button onClick={() => setLastName('Fred')}>Fred</Button>
		</div>
	) 
}

// 用来模拟 react 渲染循环
function MyComponent() {
	cursor = 0;
	return <RenderFunctionComponent />
}

console.log(state); // 预渲染 []
MyComponent();
console.log(state); // 第一次渲染 [Rudi, Yardley]
MyComponent();
console.log(state); // 后续渲染 [Rudi, Yardley]


// 点击 Fred 按钮

console.log(state); // 点击后 [Fred, Yardley]

为什么顺序很重要

如果我们改变了依赖外在因素或者组件状态的渲染循环的 hooks 的顺序后会发生什么呢?

let firstRender = true;

function RenderFucntionComponent() {
	let initName;

	if(firstRender) {
		[initName] = useState('Rudi');
		firstRender = false;
	}

	const [firstName, setFirstName] = useState[initName];

	const [lastName, setLastName] = 
		useState['Yardley'];
	
	return (
		<Button onClick={() => setFirstName('Fred')}> Fred 
		</Button>
	)	
}

这是 useState 的一个调用,我们看一下这将会怎样的破坏整个系统。

** 第一次渲染错误的组件
渲染了错误的 hook 导致数据会到下一次渲染
现在这种情况下我们的变量 firstNamelastName 都包含正确的数据,下面我们来看看第二次渲染的时候会发生什么:
第二次渲染错误的组件
想要移除渲染间的 hook 会收到一个错误
现在 firstNamelastName 都会被设置为 Rudi 因为我们的状态存储出了问题,这是一个很明显的错误,但是这个也就解释了为什么 React
核心团队设置了这么多的规则。

React 团队为 hooks API 的使用设置了这么多的规则,如果不按照它们所说的使用会导致数据错误。

**要考虑好要使用 hooks 操纵数组,如何不违反规则
你现在应该清楚为什么你不能在条件语句或循环语句中使用 hooks 了。 因为我们是在处理指向一组数组的游标,如果更改了 render 中的调用顺序,则游标将无法与数据匹配,那么调用将不会指向正确的数据或处理程序。

正确使用的诀窍就是将 hooks 作为一组需要能保证一致游标的数组来管理,这样做一切都不会出错。

结论

对于那些想要进一步理解这个新的 hooks API 的人,希望我已经构建了一个清晰的模型来帮助大家理解它。请记住它的的真正价值在于能够将关注点组合在一起,只要能够小心的注意顺序问题,那么使用 hooks API 将会感觉到很舒服。

Hooks 是 React 组件里一个高效的 API 。这也是大家为什么对它感到如此兴奋的原因,如果你在考虑使用这样的一个模型,它的状态是以数组的形式的状态存在,那你就可以考虑使用这样的方案,只需要保证按照它的规则使用即可。

同样的我还在关注将来可能会有的 useEffects 方法,在将来我会将它与 React 的组件生命周期方法进行对比。

Metadata

Metadata

Assignees

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions