官方对于原始设计理念的描述
这个文档适用于解释我心中的React模型,通过演绎推理,从而实现我们最终的设计目标。
React.js的有很多有实际用途的解决方案,例如增量步骤、算法优化、遗留代码、调试工具以及其他有用的工具。这些东西是具有可变性的,如果价值够高,优先级够高,随时可以被替换改变。
React.js本身的实现很难被完整的推理,我只是描述一下我心目中的简化的模型。
转换
React的核心是数据到UI的映射,不同的数据模型映射到不同的UI上,同样一个输入会有同样的输出(纯函数概念)
例如
function NameBox(name) { return { fontWeight: 'bold', labelContent: name }; } 'abc' -> { fontWeight: 'bold', labelContent: 'abc' };
抽象化
很多情况都无法在一个简单的函数中实现复杂的UI,这个时候就需要对一些UI进行抽象,多个抽象出来的小UI的聚合组成负责的UI。如同从一个函数调用另一个函数。
function FancyUserBox(user) { return { borderStyle: '1px solid blue', childContent: [ 'Name: ', NameBox(user.firstName + ' ' + user.lastName) ] }; } // NameBox 就是一个基本单元,最终会被聚合成一个高级的抽象 { firstName: 'abc', lastName: '123' } -> { borderStyle: '1px solid blue', childContent: [ 'Name: ', { fontWeight: 'bold', labelContent: 'abc 123' } ] };
组合
实现一个可复用的特性,单纯的只是一些基本的UI容器是不够,叶成木,也需要木成林。这就需要不同的抽象进行更高一级的聚合。
function FancyBox(children) { return { borderStyle: '1px solid blue', children: children }; } // FacnyBox本身一个抽象,UserBox是基于另一个抽象的再抽象,高阶组件了解一下 function UserBox(user) { return FancyBox([ 'Name: ', NameBox(user.firstName + ' ' + user.lastName) ]); }
State
UI不是一个简单的服务或者业务逻辑的复制,很多状态是一个特定的映射。举个栗子,如果你开始在输入框中输入,这可能会影响会其他tab或者手机设备的内容,也有可能不会。比如滚动位置大概率不会想影响其他部分。
我们倾向我们的数据模型是不可变的,这样我们更新状态的时候就可以在顶部进行原子操作。
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 );
这些例子中更新状态(state)产生了副作用,我的真实想法是更新是产生下一个版本的状态。
记忆化(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 ]); }
数组
大多数UI是相同形式的列表,然后为每个列表中的项生成多个不同的值。从而实现列表的层次结构。
要管理列表中每个Item的状态,我们可以创建一个Map来为每个特定的item保存状态
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,但是我们记忆化中只做了一个value的记忆。这打破了我们的记忆化。
连续性(Continuations)
在实际开发中,我们会遇到很多各种类型的列表,这会导致我们生成多种不同类型的模板。
为解决这个,我们可以我们通过延迟函数执行移除关键逻辑的一些模板。例如我们可以用柯里函数(bind绑定),这样我们就可以将外部的状态传递到内部函数,而不需要模板。
下个例子中没有移除模板,但是却可以从核心逻辑中移除。
function FancyUserList(users) { return FancyBox( UserList.bind(null, users) ); } // 通过bind将users传递到UserList 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)
一旦我们想在列表中记忆不同类型的item,情况就变得复杂很多,这就需要复杂的缓存算法来平衡内存使用频率。
我们可以使用在状态使用的相同方法,并通过可组合函数传递memoization缓存。也就是说对缓存函数本身增加map。
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);
代数效应
很多情况,对于一些抽象之间的传递我们并不希望进行层层嵌套传递,希望对一些中间件透明,在React我们就通过context进行。
有的时候数据的依赖不完全根据抽象树,比如,在layout算法中在某个数的叶子完全被内容填充之前就需要知道叶子的尺寸。
例子有些不贴切。
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) ); }