logo
当前位置:首 页 > 编程技术 >前端开发 > 查看文章

React作者的构思和演绎

前端开发, 编程技术 你是第2182个围观者 0条评论 供稿者:

这是React作者在React设计之初,对整个框架的思考

我通过这篇文章试图阐述我对React模型的理解,阐述我们是如何用【演绎推导】来帮助我们得到最后的设计。

当然,这里有很多的前置条件是有争议的,而且这篇文章中的例子是有缺陷和漏洞。 但这是我们正式地去规范化它。如果你有更好的想法去形式化它,请随意向我们发PR。即便在没有太多库文件和细节的情况下,从简单到复杂的演绎应该是有意义的。

最终的React实现版本,需要大量务实的解决方案、增量迭代、算法优化、遗留代码、调试工具——这些如果有实用价值,都更新太快。所以react的实现过程是很难推导的。

所以我想给大家展示一个简单的构思模型让我可以立足其中。

Transformation(变换)

React的一个核心假设就是UI是数据到试图的映射。同样的输入总是可以得到同样的输出。

function NameBox(name) {
return { fontWeight: ‘bold’, labelContent: name };
}
‘Sebastian Markbåge’ >
{ fontWeight: ‘bold’, labelContent: ‘Sebastian Markbåge’ };

抽象

你不可以在单个函数中使用复杂的UI。你需要先把界面抽象成为可以复用的部分,而且每个部分不泄露自己的实现细节(作为一个函数)。这样你就可以从一个函数调用另一个函数。

function FancyUserBox(user) {
return {
borderStyle: ‘1px solid blue’,
childContent: [
‘Name: ‘,
NameBox(user.firstName + ‘ ‘ + user.lastName)
]
};
}
{ firstName: ‘Sebastian’, lastName: ‘Markbåge’ } >
{
borderStyle: ‘1px solid blue’,
childContent: [
‘Name: ‘,
{ fontWeight: ‘bold’, labelContent: ‘Sebastian Markbåge’ }
]
};

上述代码FancyUserBox()和NameBox() 嵌套

Composition(组合)

为了实现真正可重用的特性,仅仅重用叶子节点,并在叶子节点上构造新的容器是不够的。 你需要在抽象的容器下面构造一个抽象容器的组合。 所谓的组合,就是将多个抽象合并成为一个新的抽象。

function FancyBox(children) {
return {
borderStyle: ‘1px solid blue’,
children: children
};
}

function UserBox(user) {
return FancyBox([
‘Name: ‘,
NameBox(user.firstName + ‘ ‘ + user.lastName)
]);
}

其实这里说的组合就是多个组件(数组)可以作为一个整体渲染;这里说的抽象,其实就是组件。

State(状态)

UI不是简单地复制服务端的业务逻辑和状态,有很多状态是针对一个特殊的投射。比如在文本框中输入文字不会投射到其他tab或者设备上。 滚动位置不会在多个投射间复用。(小编:投射应该就是状态流过组件函数,最终被渲染的过程。输入框中被输入的文字和滚动位置这些状态太特殊了,是针对一种特殊的投射,没有办法从服务端复用,所以才需要状态)。

我们倾向于选择的数据模型是不可变的(immutable),我们将函数串联起来,并且认为更新状态的函数在最顶端。

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(记忆)

调用一个纯函数一遍又一遍调用它其实非常浪费性能。我们可以把这个计算过程的输入和结果缓存起来。这样就不用重复计算。

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

很多UI都是列表——列表中的每一项有不同的数据,这构成了一种天然的结构。

为了管理每一个元素的状态,我们可以创造一个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(连续性)

不幸的是,UI中有太多的列表的列表,最后需要写很多的重复代码(boilerplate code)。

我们可以通过延迟执行函数,把一部分重复代码移出关键业务。例如:我们可以使用柯里化方法(bind in 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

当我们试图记住列表中的多个项时,记忆变得更加困难。你需要找到一些复杂的缓存算法去平衡内存使用和频率。

庆幸的是,UI在同一位置是相对稳定的。树的相同位置总是得到相同的结果。这让树成为记忆的有效策略。

这样我们可以用同样的技术去缓存组合函数。

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

React看上去像个PITA饼,一层又一层的,一个很小的值也需要这样传递。 这样我们在不同层之间需要一个短路——”context”

有时候数据依赖并没有很好地遵循抽象树,举个例子,在布局算法中你需要事先知道子元素的的大小。

接下来的这个例子有点超出我们目前讨论的范围。我用Algebraic Effects(类似副作用的一个词汇),ECMAScript提出的来解释。如果你对函数式编程非常熟悉,他们在避免monads实践过程中炸了(they’re avoiding the intermediate ceremony imposed by monads)。

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)
);
}

点评:

  • 大巧不工——被作者这样一说,复杂的东西其实也很简单。
  • 千里之行始于足下,学好简单的知识点,可以搞大事情
说说梦想,谈谈感悟 ,聊聊技术,有啥要说的来github留言吧 https://github.com/cjx2328

—— 陈 建鑫

陈建鑫
footer logo
未经许可请勿自行使用、转载、修改、复制、发行、出售、发表或以其它方式利用本网站之内容。站长联系:cjx2328#126.com(修改#为@)
Copyright ©ziao Studio All Rights Reserved. E-mail:cjx2328#126.com(#号改成@) 沪ICP备14052271号-3