🌞

React原始设计思想

官方对于原始设计理念的描述 原文地址:https://github.com/reactjs/react-basic这个文档适用于解释我心中的React模型,通过演绎推理,从而实现我们最终的设计目标。R

文链接在语雀:https://www.yuque.com/wumingshi/rkh1qq/

官方对于原始设计理念的描述

 原文地址:https://github.com/reactjs/react-basic


这个文档适用于解释我心中的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)
  );
}


updatedupdated2021-01-212021-01-21