React 設計思想

2017-05-18 1597 3 編輯:深圳網站建設 來源:互聯網

不可否認,此文中的部分論據或前提尚存爭議,而且部分示例的設計可能存在 bug 或疏忽。這只是正式確定它的最初階段。如果你有更好的完善它的想法可以隨時提交 pull request。本文不會介紹框架細節中的奇技淫巧,相信這樣能提綱挈領,讓你看清 React 由簡單到復雜的設計過程。

React.js 的真實實現中充滿了具體問題的解決方案,漸進式的解法,算法優化,歷史遺留代碼,debug 工具以及其他一些可以讓它真的具有高可用性的內容。這些代碼可能并不穩定,因為未來瀏覽器的變化和功能權重的變化隨時面臨改變。所以具體的代碼很難徹底解釋清楚。

我偏向于選擇一種我能完全 hold 住的簡潔的心智模型來作介紹。

變換(Transformation)

設計 React 的核心前提是認為 UI 只是把數據通過映射關系變換成另一種形式的數據。同樣的輸入必會有同樣的輸出。這恰好就是純函數。

function NameBox(name) {

  return { fontWeight: 'bold', labelContent: name };

}

'Sebastian Markb?ge' ->

{ fontWeight: 'bold', labelContent: 'Sebastian Markb?ge' };

抽象(Abstraction)

你不可能僅用一個函數就能實現復雜的 UI。重要的是,你需要把 UI 抽象成多個隱藏內部細節,又可復用的函數。通過在一個函數中調用另一個函數來實現復雜的 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' }

  ]

};

組合(Composition)

為了真正達到重用的特性,只重用葉子然后每次都為他們創建一個新的容器是不夠的。你還需要可以包含其他抽象的容器再次進行組合。我理解的“組合”就是將兩個或者多個不同的抽象合并為一個。

function FancyBox(children) {

  return {

    borderStyle: '1px solid blue',

    children: children

  };

}

function UserBox(user) {

  return FancyBox([

    'Name: ',

    NameBox(user.firstName + ' ' + user.lastName)

  ]);

}

狀態(State)

UI 不單單是對服務器端或業務邏輯狀態的復制。實際上還有很多狀態是針對具體的渲染目標。舉個例子,在一個 text field 中打字。它不一定要復制到其他頁面或者你的手機設備。滾動位置這個狀態是一個典型的你幾乎不會復制到多個渲染目標的。

我們傾向于使用不可變的數據模型。我們把可以改變 state 的函數串聯起來作為原點放置在頂層。

function FancyNameBox(user, likes, onClick) {

  return FancyBox([

    'Name: ', NameBox(user.firstName + ' ' + user.lastName),

    'Likes: ', LikeBox(likes),

    LikeButton(onClick)

  ]);

// 實現細節 

var likes = 0;

function addOneMoreLike() {

  likes++;

  rerender();

}

// 初始化 

FancyNameBox(

  { firstName: 'Sebastian', lastName: 'Markb?ge' },

  likes,

  addOneMoreLike

);

注意:本例更新狀態時會帶來副作用(addOneMoreLike 函數中)。我實際的想法是當一個“update”傳入時我們返回下一個版本的狀態,但那樣會比較復雜。此示例待更新

Memoization

對于純函數,使用相同的參數一次次調用未免太浪費資源。我們可以創建一個函數的 memorized 版本,用來追蹤最后一個參數和結果。這樣如果我們繼續使用同樣的值,就不需要反復執行它了。

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 都是展示列表數據中不同 item 的列表結構。這是一個天然的層級。

為了管理列表中的每一個 item 的 state ,我們可以創造一個 Map 容納具體 item 的 state。

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 傳了多個不同的參數。這打破了我們的 memoization 因為我們每次只能存儲一個值。更多相關內容在下面。

連續性(Continuations)

不幸的是,自從 UI 中有太多的列表,明確的管理就需要大量的重復性樣板代碼。

我們可以通過推遲一些函數的執行,進而把一些模板移出業務邏輯。比如,使用“柯里化”(JavaScript 中的 bind)。然后我們可以從核心的函數外面傳遞 state,這樣就沒有樣板代碼了。

下面這樣并沒有減少樣板代碼,但至少把它從關鍵業務邏輯中剝離。

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

之前我們知道可以使用組合避免重復執行相同的東西這樣一種重復模式。我們可以把執行和傳遞 state 邏輯挪動到被復用很多的低層級的函數中去。


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 列表中 memoize 多個 item 就會變得很困難。因為你需要制定復雜的緩存算法來平衡調用頻率和內存占有率。

還好 UI 在同一個位置會相對的穩定。相同的位置一般每次都會接受相同的參數。這樣以來,使用一個集合來做 memoization 是一個非常好用的策略。

我們可以用對待 state 同樣的方式,在組合的函數中傳遞一個 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)

多層抽象需要共享瑣碎數據時,一層層傳遞數據非常麻煩。如果能有一種方式可以在多層抽象中快捷地傳遞數據,同時又不需要牽涉到中間層級,那該有多好。React 中我們把它叫做“context”。

有時候數據依賴并不是嚴格按照抽象樹自上而下進行。舉個例子,在布局算法中,你需要在實現他們的位置之前了解子節點的大小。

現在,這個例子有一點超綱。我會使用 代數效應 這個由我發起的 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)

  );

}

專業的網站建設公司,深正互聯,如您有網站營銷需求,請您關注我們,或者致電13828884598

本站文章均為深正網站建設摘自權威資料,書籍,或網絡原創文章,如有版權糾紛或者違規問題,請即刻聯系我們刪除,我們歡迎您分享,引用和轉載,但謝絕直接搬磚和抄襲!感謝...
關注深正互聯
我們猜你喜歡
七星彩头尾