这个文件是我试图正式解释我关于React的心理模型。 我们的目的是用演绎推理的方式来描述这一点,从而引导我们进行这种设计。
可能肯定会存在一些有争议的前提,并且此示例的实际设计可能存在缺陷和差距。 这只是形式化的开始。 如果您对如何正式定位有更好的想法,请随时发送拉取请求。 从简单 - >复杂的过程应该是有道理的,而不需要太多的图书细节。React.js的实际操作包括了很多实用的解决方案,增量步骤,算法优化,遗留代码,调试工具以及使其实际使用所需的东西。 这些东西更短暂,如果它足够有价值并且具有足够的优先级,它可以随着时间而改变。 但是实际的实施其实难以推理。
所以我喜欢有一个我可以将自己置身于其中的更简单的心理模型。
Transformation ——转型
React的核心思想是,仅仅是将数据渲染到不同形式界面的数据中。 相同的输入提供相同的输出。实例: 一个简单的纯函数。
function NameBox(name) {
return { fontWeight: 'bold', labelContent: name };
}
'Sebastian Markbåge' ->
{ fontWeight: 'bold', labelContent: 'Sebastian Markbåge' };
Abstraction——抽象化
尽管如此,你不能在一个单独的函数中容纳一个复杂的UI。 UI可以抽象成不会泄露其实现细节的可重用部分,这一点很重要。 如从另一个调用一个函数。
function FancyUserBox(user) {
return {
borderStyle: '1px solid blue',
childContent: [
'Name: ',
NameBox(user.firstName + ' ' + user.lastName)
]
};
}
FancyUserBox 接收到传入的user对象,得到了firstName和lastName后又调取了NamBox函数。
{ firstName: 'Sebastian', lastName: 'Markbåge' } ->
{
borderStyle: '1px solid blue',
childContent: [
'Name: ',
{ fontWeight: 'bold', labelContent: 'Sebastian Markbåge' }
]
};
Composition——组件
为了实现真正的可重用特性,简单地重用并为它们构建新的容器是不够的。 您还需要能够从构成其他抽象的容器中构建抽象。 我认为“构图”的方式是,他们将两个或更多不同的抽象组合成一个新的抽象。
function FancyBox(children) {
return {
borderStyle: '1px solid blue',
children: children
};
}
function UserBox(user) {
return FancyBox([
'Name: ',
NameBox(user.firstName + ' ' + user.lastName)
]);
}
这是将FancyBox与UserBox 组成了一个函数,用户只需调取UserBox即可。
State——状态
UI不仅仅是服务器/业务逻辑状态的复制。 实际上有很多状态是特定于确切的预测而不是其他的。 例如,如果您开始在文本字段中输入内容。 这可能会或可能不会复制到其他选项卡或移动设备。 滚动位置是一个典型的例子,您几乎从不想在多个投影中进行复制。
我们倾向于选择我们的数据模型是不可变的。 我们通过线程函数可以将状态更新为顶部的单个原子。
function FancyNameBox(user, likes, onClick) {
return FancyBox([
'Name: ', NameBox(user.firstName + ' ' + user.lastName),
'Likes: ', LikeBox(likes),
LikeButton(onClick)
]);
}
// Implementation Details 实施细节
var likes = 0;
function addOneMoreLike() {
likes++;
rerender();
}
// Init 初始化
FancyNameBox(
{ firstName: 'Sebastian', lastName: 'Markbåge' },
likes,
addOneMoreLike
);
注意:这些示例使用副作用来更新状态。 我的实际心理模型是,他们在“更新”过程中返回下一个版本的状态。 如果没有这个解释就更简单了,但我们会在未来想改变这些例子。
Memoization——记忆化
如果我们知道函数是纯粹的,那么一次又一次调用同一个函数是浪费的。 我们可以创建一个追踪最后一个参数和最后结果的函数的memoized版本。 这样,如果我们继续使用相同的值,我们不必重新执行它。
function memoize(fn) {
var cachedArg;
var cachedResult;
return function(arg) {
if (cachedArg === arg) {
return cachedResult;
}
cachedArg = arg;
cachedResult = fn(arg);
return cachedResult;
};
}
var MemoizedNameBox = memoize(NameBox);
function NameAndAgeBox(user, currentTime) {
return FancyBox([
'Name: ',
MemoizedNameBox(user.firstName + ' ' + user.lastName),
'Age in milliseconds: ',
currentTime - user.dateOfBirth
]);
}
Lists——列表
大多数用户界面都是某种形式的列表,然后为列表中的每个项目生成多个不同的值。 这创建了一个自然的层次。
要管理列表中每个项目的状态,我们可以创建一个包含特定项目状态的Map。
function UserList(users, likesPerUser, updateUserLikes) {
return users.map(user => FancyNameBox(
user,
likesPerUser.get(user.id),
() => updateUserLikes(user.id, likesPerUser.get(user.id) + 1)
));
}
var likesPerUser = new Map();
function updateUserLikes(id, likeCount) {
likesPerUser.set(id, likeCount);
rerender();
}
UserList(data.users, likesPerUser, updateUserLikes);
注意:我们现在有多个不同的参数传递给FancyNameBox。
这打破了我们的记忆,因为我们一次只能记住一个值。 更多请看下面的内容。
Continuations——延续
不幸的是,由于在用户界面中有很多列表,所以需要很清晰地管理这些特别多的列表。
我们可以通过推迟执行某个功能来将这些模板从我们的关键业务逻辑中移出。 例如,通过使用“currying”(在JavaScript中绑定)。然后我们通过现在核心功能的外部传递状态。
这不是在降低标准,但至少是动了关键的业务逻辑。
function FancyUserList(users) {
return FancyBox(
UserList.bind(null, users)
);
}
const box = FancyUserList(data.users);
const resolvedChildren = box.children(likesPerUser, updateUserLikes);
const resolvedBox = {
...box,
children: resolvedChildren
};
State Map——状态地图
我们从前面知道,一旦我们看到重复的模式,我们可以使用组件来避免重复实现相同的模式。 我们可以将提取和传递状态的逻辑转移到一个我们重复使用的低级函数。
function FancyBoxWithState(
children,
stateMap,
updateState
) {
return FancyBox(
children.map(child => child.continuation(
stateMap.get(child.key),
updateState
))
);
}
function UserList(users) {
return users.map(user => {
continuation: FancyNameBox.bind(null, user),
key: user.id
});
}
function FancyUserList(users) {
return FancyBoxWithState.bind(null,
UserList(users)
);
}
const continuation = FancyUserList(data.users);
continuation(likesPerUser, updateUserLikes);
Memoization Map——记忆地图
一旦我们想记忆列表中的多个项目,记忆就变得更加困难。 你必须弄清楚一些复杂的缓存算法,以平衡内存使用与频率。
幸运的是,用户界面在相同位置往往相当稳定。 树中的相同位置每次都获得相同的值。 这棵树变成了一个真正有用的记忆策略。
我们可以使用我们用于状态的相同技巧,并通过可组合函数传递memoization缓存。
function memoize(fn) {
return function(arg, memoizationCache) {
if (memoizationCache.arg === arg) {
return memoizationCache.result;
}
const result = fn(arg);
memoizationCache.arg = arg;
memoizationCache.result = result;
return result;
};
}
function FancyBoxWithState(
children,
stateMap,
updateState,
memoizationCache
) {
return FancyBox(
children.map(child => child.continuation(
stateMap.get(child.key),
updateState,
memoizationCache.get(child.key)
))
);
}
const MemoizedFancyNameBox = memoize(FancyNameBox);
Algebraic Effects——代数效应
事实证明,这是一种PITA,可以通过几个层次的抽象来传递您可能需要的每个小小的值。 有时候有一条捷径可以在两个抽象之间传递事物,而不涉及中间体。 在React中,我们称之为“上下文”。
有时候,数据依赖并不整齐地跟随抽象树。 例如,在布局算法中,您需要先了解一些关于子集的大小的内容,然后才能完全实现自己的位置。
现在,这个例子有点“偏移”。 我将使用ECMAScript提出的代数效果效应。 如果你熟悉函数式编程,他们会避免monad强加的中间仪式。
function ThemeBorderColorRequest() { }
function FancyBox(children) {
const color = raise new ThemeBorderColorRequest();
return {
borderWidth: '1px',
borderColor: color,
children: children
};
}
function BlueTheme(children) {
return try {
children();
} catch effect ThemeBorderColorRequest -> [, continuation] {
continuation('blue');
}
}
function App(data) {
return BlueTheme(
FancyUserList.bind(null, data.users)
);
}
原文:https://github.com/reactjs/react-basic#react---basic-theoretical-concepts