経験は何よりも饒舌

10年後に真価を発揮するかもしれないブログ 

Reactのコードを読む(3)

前回はhello worldが描写されるところまでをみたので、今回はuseStateをみていこうと思う。

wafuwafu13.hatenadiary.com

まずは、useStateがどこで定義されているのかを確認する。

<!DOCTYPE html>
<html>
<head>
    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
</head>
<body>
</div>
<script>
    console.log(React);
</script>
</body>
</html>

この出力は、以下のようになる。

Children: {map: ƒ, forEach: ƒ, count: ƒ, toArray: ƒ, only: ƒ}
Component: ƒ Component(props, context, updater)
Fragment: Symbol(react.fragment)
...
useEffect: ƒ useEffect(create, deps)
useImperativeHandle: ƒ useImperativeHandle(ref, create, deps)
useLayoutEffect: ƒ useLayoutEffect(create, deps)
useMemo: ƒ useMemo(create, deps)
useReducer: ƒ useReducer(reducer, initialArg, init)
useRef: ƒ useRef(initialValue)
useState: ƒ useState(initialState)
...

つまり、useStateReactのプロパティとして定義されていて、コードでは3552行目でエクスポートされている。
https://github.com/wafuwafu13/react-17-react-dom-17/blob/master/react.development.js#L3552

exports.useState = useState;

次は、console.log(React.useState);によってuseStateを出力してみる。

ƒ useState(initialState) {
    var dispatcher = resolveDispatcher();
    return dispatcher.useState(initialState);
  }

結果をみると、useStateは関数だとわかる。
実際にコードを見にいくと、1531行目に定義されている。
https://github.com/wafuwafu13/react-17-react-dom-17/blob/master/react.development.js#L1531

function useState(initialState) {
      var dispatcher = resolveDispatcher();
      return dispatcher.useState(initialState);
}

では、実際にconsole.log(React.useState());で呼び出してみる。
すると、以下のエラーがでる。

Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.

このエラーは、1532行目で呼び出されていたresolveDispatcher関数の内部の1501行目で起こっている。
https://github.com/wafuwafu13/react-17-react-dom-17/blob/master/react.development.js#L1501

    function resolveDispatcher() {
      var dispatcher = ReactCurrentDispatcher.current;
  
      if (!(dispatcher !== null)) {
        {
          throw Error( "Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:\n1. You might have mismatching versions of React and the renderer (such as React DOM)\n2. You might be breaking the Rules of Hooks\n3. You might have more than one copy of React in the same app\nSee https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem." );
        }
      }
  
      return dispatcher;
}

このエラーは、if (!(dispatcher !== null))trueのとき、つまりdispatchernullのとき生じる。
そのdispatcherに代入されているReactCurrentDispatcherは、115行目で定義されており、確かにReactCurrentDispatcher.currentnullである。
https://github.com/wafuwafu13/react-17-react-dom-17/blob/master/react.development.js#L115

    var ReactCurrentDispatcher = {
      /**
       * @internal
       * @type {ReactComponent}
       */
      current: null
    };

エラー内容に、Hooks can only be called inside of the body of a function component.とあったので、function componentを定義し、その内部でuseStateを呼び出してみる。

そのためにはまず、コンポーネントを作成しないといけない。
コンポーネントの作成には、createElement()を用いる。
公式のReact 要素を作成するに、「JSX のそれぞれの要素は React.createElement() を呼ぶための単なる糖衣構文です。」という説明がなされており、今回はuseStateをみるのが目的なので深追いはしない。
では、以下のようにhelloコンポーネントを作成し、画面にhello worldを描写する。
そのhelloコンポーネント内でuseStateを呼び出して出力してみる。

<body>
    <div id="root"></div>
    <script>
      const hello = () => {
        console.log(React.useState());
        return React.createElement("div", null, "hello world");
      };

      ReactDOM.render(
        React.createElement(hello),
        document.getElementById("root")
      );
    </script>
</body>

出力は以下のように、1つ目の要素がundefined、2つ目の要素が関数の配列が返ってくる。

(2) [undefined, ƒ]
0: undefined
1: ƒ ()
arguments: (...)
caller: (...)
length: 1
name: "bound dispatchAction"
__proto__: ƒ ()
[[TargetFunction]]: ƒ dispatchAction(fiber, queue, action)
[[BoundThis]]: null
[[BoundArgs]]: Array(2)
length: 2
__proto__: Array(0)

先ほどnullになってエラーの原因となっていたdispatcherをみてみると、useStateが含まれていた。

readContext: ƒ (context, observedBits)
unstable_isNewReconciler: false
useCallback: ƒ (callback, deps)
...
useState: ƒ (initialState)
...


dispatcherに代入されているReactCurrentDispatcher.currentがどのように設定されているのかは、以下の記事がわかりやすかった。
renderWithHooks関数は14976行目に定義されている。
https://github.com/wafuwafu13/react-17-react-dom-17/blob/master/react-dom.development.js#L14976

sbfl.net


resolveDispatcherが呼び出されたあとは、dispatcher.useStateが呼び出されている。
このuseStateは16271行目に定義されている。

useState: function (initialState) {
          currentHookNameInDev = 'useState';
          mountHookTypesDev();
          var prevDispatcher = ReactCurrentDispatcher$1.current;
          ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnMountInDEV;
  
          try {
            return mountState(initialState);
          } finally {
            ReactCurrentDispatcher$1.current = prevDispatcher;
          }
},

これはHooksDispatcherOnMountInDEVのプロパティであり、15011行目でReactCurrentDispatcherに代入されている。
https://github.com/wafuwafu13/react-17-react-dom-17/blob/master/react-dom.development.js#L15011

ReactCurrentDispatcher$1.current = HooksDispatcherOnMountInDEV;

useStateの内部ではReactCurrentDispatcherの更新が行われ、15651行目で定義されているmountState関数が呼び出されている。
https://github.com/wafuwafu13/react-17-react-dom-17/blob/master/react-dom.development.js#L15651

    function mountState(initialState) {
      var hook = mountWorkInProgressHook();
  
      if (typeof initialState === 'function') {
        // $FlowFixMe: Flow doesn't like mixed types
        initialState = initialState();
      }
  
      hook.memoizedState = hook.baseState = initialState;
      var queue = hook.queue = {
        pending: null,
        dispatch: null,
        lastRenderedReducer: basicStateReducer,
        lastRenderedState: initialState
      };
      var dispatch = queue.dispatch = dispatchAction.bind(null, currentlyRenderingFiber$1, queue);
      return [hook.memoizedState, dispatch];
    }

そこでhook.memoizedStateに引数で受け取ったinitialStateが代入され、返り値の配列の第一引数になっている。
第二引数のdispatchは、16805行目のdispatchActionbindしたものが入っている。

これらの返り値を以下のように分割代入して用いる。

const [foo, setFoo] = useState(bar);

深追いできなかった部分は多かったが、以上でsetStateを読むのは終える。
ReactCurrentDispatcherの更新が沼すぎるのでこれ以上Reactを読むかどうかは分からない、とても同じ人間が書いたとは思えない。
次回からはフロントの繋がりでBabelを読むか、Goのechoとかを読むかもしれない。
DOMの更新に関しては以下の記事がわかりやすかったのでこれを深めると良いと思っている。

kuroeveryday.blogspot.com

これもよさそう。
(翻訳) React Hooks は魔法ではなく、ただの配列だ · GitHub

Reactのコードを読む(2)

前回はReactDOMについてみたので、今回はrenderhello worldが描写されるところまでみていこうと思う。

wafuwafu13.hatenadiary.com

まずは、ReactDom.renderを出力してみる。

<!DOCTYPE html>
<html>
<head>
    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
</head>
<body>
</div>
<script>
    console.log(ReactDOM.render());
</script>
</body>
</html>

すると、以下のようなエラーが発生する。

Uncaught Error: Target container is not a DOM element.
    at Object.render (react-dom.development.js:26121)
    at index.html:11

エラーが発生しているのは26121行目で、!isValidContainer(container)trueになっている。
https://github.com/wafuwafu13/react-17-react-dom-17/blob/master/react-dom.development.js#L26121

function render(element, container, callback) {
      if (!isValidContainer(container)) {
        {
          throw Error( "Target container is not a DOM element." );
        }
      }
      ...

isValidContainer関数は25932行目にある。
https://github.com/wafuwafu13/react-17-react-dom-17/blob/master/react-dom.development.js#L25932

function isValidContainer(node) {
      return !!(node && (node.nodeType === ELEMENT_NODE || node.nodeType === DOCUMENT_NODE || node.nodeType === DOCUMENT_FRAGMENT_NODE || node.nodeType === COMMENT_NODE && node.nodeValue === ' react-mount-point-unstable '));
}

今回は引数のnodeundefinedであるため、falseが返り、!isValidContainer(container)trueとなる。

では、以下のコードで実際にhello worldを表示させた状態でみていく。

<div id="root"></div>
<script>
    console.log(ReactDOM.render(
        "hello world",
        document.getElementById('root')
    ));
</script>
</body>
</html>

render関数の第二引数であるdocument.getElementById('root')nodeTypeを出力すると、1であった。

console.log(document.getElementById('root').nodeType); // 1

Node.nodeType1ということはつまりELEMENT_NODEであるから、isValidContainer関数はtrueを返す。

続いて、26126行目をみてみる。
https://github.com/wafuwafu13/react-17-react-dom-17/blob/master/react-dom.development.js#L26126

var isModernRoot = isContainerMarkedAsRoot(container) && container._reactRootContainer === undefined;


まずは、isContainerMarkedAsRoot関数が定義されている10597行目をみてみる。
https://github.com/wafuwafu13/react-17-react-dom-17/blob/master/react-dom.development.js#L10597

function isContainerMarkedAsRoot(node) {
    return !!node[internalContainerInstanceKey];
}

internalContainerInstanceKeyは、10583行目で生成されているrandomKeyを含む変数であり、10586行目に定義されている。
https://github.com/wafuwafu13/react-17-react-dom-17/blob/master/react-dom.development.js#L10583
https://github.com/wafuwafu13/react-17-react-dom-17/blob/master/react-dom.development.js#L10586

var randomKey = Math.random().toString(36).slice(2);
...
var internalContainerInstanceKey = '__reactContainer$' + randomKey;

よって、最終的に例えばnode["__reactContainer$dgg96aihpip"]の存在が調べられ、falseが返り、26110行目の条件分岐は通らない。
https://github.com/wafuwafu13/react-17-react-dom-17/blob/master/react-dom.development.js#L26110

if (isModernRoot) {
          error('You are calling ReactDOM.hydrate() on a container that was previously ' + 'passed to ReactDOM.createRoot(). This is not supported. ' + 'Did you mean to call createRoot(container, {hydrate: true}).render(element)?');
}

続く26133行目では、関数の返り値を返している。
https://github.com/wafuwafu13/react-17-react-dom-17/blob/master/react-dom.development.js#L26133

return legacyRenderSubtreeIntoContainer(null, element, container, false, callback);

legacyRenderSubtreeIntoContainer関数は26024行目に定義されており、その中で26032行目にrootが定義されている。
https://github.com/wafuwafu13/react-17-react-dom-17/blob/master/react-dom.development.js#L26032

var root = container._reactRootContainer;

div #rootには_reactRootContainerは存在せず、rootundefinedになる。
よって、26035行目のif (!root)を通り、26037行目でrootが代入される。
https://github.com/wafuwafu13/react-17-react-dom-17/blob/master/react-dom.development.js#L26037

root = container._reactRootContainer = legacyCreateRootFromDOMContainer(container, forceHydrate);

legacyCreateRootFromDOMContainerは25983行目に定義されており、26011行目で関数の返り値を返している。
https://github.com/wafuwafu13/react-17-react-dom-17/blob/master/react-dom.development.js#L26011

return createLegacyRoot(container, shouldHydrate ? {
    hydrate: true
} : undefined);

そのcreateLegacyRootは25929行目に定義されており、25930行目でインスタンスを返している。
https://github.com/wafuwafu13/react-17-react-dom-17/blob/master/react-dom.development.js#L25930

return new ReactDOMBlockingRoot(container, LegacyRoot, options);

結果的に、rootは以下のような値になる。

_internalRoot: FiberRootNode
callbackNode: null
callbackPriority: 0
containerInfo: div#root
context: null
current: FiberNode {tag: 3, key: null, elementType: null, type: null, stateNode: FiberRootNode, …}
entangledLanes: 0
entanglements: (31) [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
....

fiberRootの値はroot._internalRootであり、以下のようになる。

callbackNode: null
callbackPriority: 0
containerInfo: div#root
context: null
current: FiberNode {tag: 3, key: null, elementType: null, type: null, stateNode: FiberRootNode, …}
entangledLanes: 0
entanglements: (31) [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
eventTimes: (31) [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
expirationTimes: (31) [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1]

このcontainerInfo: div#rootには、document.getElementById('root')で取得したDOMの情報が含まれている。

...
outerHTML: "<div id="root"></div>"
outerText: ""
ownerDocument: document
URL: "file:///Users/tagawahirotaka/Desktop/react-17-react-dom-17/index.html"
...

今までみてきたlegacyRenderSubtreeIntoContainerは、最終的に26070行目で関数の返り値を返している。
https://github.com/wafuwafu13/react-17-react-dom-17/blob/master/react-dom.development.js#L26070

return getPublicRootInstance(fiberRoot);

getPublicRootInstanceは25515行目で定義されており、25527行目でNodeを返している。
https://github.com/wafuwafu13/react-17-react-dom-17/blob/master/react-dom.development.js#L25527

return containerFiber.child.stateNode;

このcontainerFiber.child.stateNodeには、renderの第一引数で渡したhello worldnodeValuetextContentとして含まれている。

nodeName: "#text"
nodeType: 3
nodeValue: "hello world"
ownerDocument: document
parentElement: div#root
parentNode: div#root
previousElementSibling: null
previousSibling: null
textContent: "hello world"

このNodeが返されることにより、画面にhello worldが描写される。
途中の処理を結構飛ばしたので理解が曖昧だが、次はuseStateなどをみていきたい。

wafuwafu13.hatenadiary.com

『リファクタリング(第2版): 既存のコードを安全に改善する』をTypeScriptで実装

最近、フロントエンドのコードを整理する機会が増えてきたので、ただ型をつけるだけではなくて、構造を捉えたリファクタリングができるようになりたいと思い、進めている。

第2版のサンプルコードはJavaからJavaScriptになっており、せっかくなのでTypeScriptを導入することにした。
1章まで終え、関数の抽出や、ポリモーフィズムによる条件記述の置き換えなど、学ことは多かったが、ここでは、型をつけるにあたって少し詰まったところを列挙していく。

リポジトリはこちら
github.com

JSONの読み込み

TypeScript導入 · wafuwafu13/Refactoring-Improving-the-Design-of-Existing-Code@4483210 · GitHub

--resolveJsonModuleオプションをつければ自動で型がつくようになる。

今回は、.d.tsファイルを作成して自分で型定義をした。

mapの返り値の型

27 · wafuwafu13/Refactoring-Improving-the-Design-of-Existing-Code@56125ae · GitHub

以下のように、mapで生成する新しい配列の中のオブジェクトに新たなプロパティがたされる場面があった。

const invoice = {
  customer: "BigCo",
  performances: [
    {
      playID: "hamlet",
      audience: 55,
    },
    {
      playID: "as-like",
      audience: 35,
    },
    {
      playID: "othello",
      audience: 40,
    },
  ],
};

const statementData = {};

statementData.customer = invoice.customer;

statementData.performances = invoice.performances.map((aPerformance) => {
  const result = Object.assign({}, aPerformance);
  result.play = "hamlet";
  return result;
});

まず、この状態だと、statementData.customerの部分に、Property 'customer' does not exist on type '{}'.というエラーが出る。
これは、statementDataの型が{}であるからだ。
よって、型を定義する。

type StatementData = {
    customer?: string
}

const statementData: StatementData = {};

statementData.customer = invoice.customer;

customer?をつけることにより、

const statementData: StatementData = {};

としたときに、Property 'customer' is missing in type '{}' but required in type 'StatementData'.というエラーが出ないようにする。

続いて、

statementData.performances = invoice.performances.map((aPerformance) => {
  const result = Object.assign({}, aPerformance);
  result.play = "hamlet";
  return result;
});

で、Property 'performances' does not exist on type 'StatementData'.のエラーが出ないように、ひとまずanyで型定義をしておく。

type StatementData = {
    customer?: string
    performances?: any
}

すると、result.play = "hamlet";の部分に、Property 'play' does not exist on type '{ playID: string; audience: number; }'.というエラーが出る。

これは、const result = Object.assign({}, aPerformance);で定義されたresultの型が

const result: {
    playID: string;
    audience: number;
}

であるからだ。

なぜresultの型がそうなるのかというと、invoice.performancesの型が以下であり、

(property) performances: {
    playID: string;
    audience: number;
}[]

aPerformanceの型がresultに反映されているからだ。

よって、aPerformanceの型を定義するために、先ほどanyにしていた部分をきちんと定義する。

type StatementData = {
  customer?: string;
  performances?: {
    playID: string;
    audience: number;
    play?: string;
  }[];
};

そして、このperformancesの型をIndexed Access Typesを用いてaPerformanceに当てていく。

statementData.performances = invoice.performances.map(
  (aPerformance: StatementData["performances"][0]) => {
    const result = Object.assign({}, aPerformance);
    result.play = "hamlet";
    return result;
  }
);

しかし、StatementData["performances"][0]の部分で、Property '0' does not exist on type '{ play?: string | undefined; }[] | undefined'.のエラーが出る。

これは、StatementData["performances"]undefinedの可能性があるからであり、Requiredを用いてその可能性を潰せばよい。

statementData.performances = invoice.performances.map(
  (aPerformance: Required<StatementData>["performances"][0]) => {
    const result = Object.assign({}, aPerformance);
    result.play = "hamlet";
    return result;
  }
);

そうすることで、resultの型は以下のようになる。

const result: {
    playID: string;
    audience: number;
    play?: string | undefined;
}

最終的なコードは以下のようになる。

const invoice = {
  customer: "BigCo",
  performances: [
    {
      playID: "hamlet",
      audience: 55,
    },
    {
      playID: "as-like",
      audience: 35,
    },
    {
      playID: "othello",
      audience: 40,
    },
  ],
};

type StatementData = {
  customer?: string;
  performances?: {
    playID: string;
    audience: number;
    play?: string;
  }[];
};

const statementData: StatementData = {};

statementData.customer = invoice.customer;

statementData.performances = invoice.performances.map(
  (aPerformance: Required<StatementData>["performances"][0]) => {
    const result = Object.assign({}, aPerformance);
    result.play = "hamlet";
    return result;
  }
);
superで親のメソッドを呼び出す

41 · wafuwafu13/Refactoring-Improving-the-Design-of-Existing-Code@a4293c7 · GitHub

superECMAScript2015/ES6の仕様なので、tsconfig.jsontargetes6にしないといけなかった。


=============================

以降、読んだだけでは理解しづらかった箇所をメモ。

p178 コレクションのカプセル化
class Person {
  constructor(name) {
    this._name = name;
    this._courses = [];
  }
  get name() {
    return this._name;
  }
  get courses() {
    return this._courses;
  }
  set courses(aList) {
    this._courses = aList;
  }
}

class Courses {
  constructor(name, isAdvanced) {
    this._name = name;
    this._isAdvanced = isAdvanced;
  }
  get name() {
    return this._name;
  }
  get isAdvanced() {
    return this._isAdvanced;
  }
}

const aPerson = new Person("foo");

aPerson.courses.push(new Courses("bar", false));
console.log(aPerson.courses); // [ Courses { _name: 'bar', _isAdvanced: false } ]

aPerson.courses.pushcoursesが更新できてしまうと、Personクラスは変更をコントロールできていない、つまり、setterが機能していない。

class Person {
  constructor(name) {
    this._name = name;
    this._courses = [];
  }
  get name() {
    return this._name;
  }
  get courses() {
    return this._courses.slice();
  }
  addCourse(aCourse) {
    this._courses.push(aCourse);
  }
}

const aPerson = new Person("foo");

aPerson.addCourse(new Courses("bar", false));
aPerson.courses.push(new Courses("hoge", true))
aPerson.courses.push(new Courses("fuga", true))

console.log(aPerson.courses); // [ Courses { _name: 'bar', _isAdvanced: false } ]

addCourseメソッドをPersonクラスに定義し、getterはthis._courses.slice()によりコピーを返すことで、aPerson.courses.pushといった方法でcoursesを変更できないようにする。

Reactのコードを読む(1)

この前jQueryのコードを読んでなんとなくコードリーディングがわかってきたので今回はReactのコードを読んでいこうと思う。
wafuwafu13.hatenadiary.com

直近の目標は、以下のコードでどのようにして画面にhello worldが表示されるかを解明することである。

<!DOCTYPE html>
<html>
<head>
    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.js"></script>
</head>
<body>
<div id="root">
</div>
<script type="text/babel">
    ReactDOM.render(
        <h1>hello world</h1>,
        document.getElementById('root')
    );
</script>
</body>
</html>

コードは今回も行数がわかりやすいようにGitHubにあげた。
github.com


まずは、ReactDOMが何なのかから見ていこうと思う。

<!DOCTYPE html>
<html>
<head>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
</head>
<body>
<div id="root">
</div>
<script>
    console.log(ReactDOM)
</script>
</body>
</html>

このコードの出力結果は、以下のようなエラーである。

Uncaught TypeError: Cannot read property '__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED' of undefined
    at react-dom.development.js:15
    at react-dom.development.js:12
    at react-dom.development.js:13

react-dom.development.jsの15行目を見てみると、以下のような記述がある。
https://github.com/wafuwafu13/react-17-react-dom-17/blob/master/react-dom.development.js#L15

var ReactSharedInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;

この__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIREDは、react.development.jsの3533行目でエクスポートされている。
https://github.com/wafuwafu13/react-17-react-dom-17/blob/master/react.development.js#L3533

exports.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = ReactSharedInternals$1;

よって、react.development.jsも読み込まないといけない。

<!DOCTYPE html>
<html>
<head>
    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
</head>
<body>
<div id="root">
</div>
<script>
    console.log(ReactDOM)
</script>
</body>
</html>

今回の出力結果は、以下のようになる。

▽{__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: {…}, createPortal: ƒ, findDOMNode: ƒ, flushSync: ƒ, hydrate: ƒ, …}
 ▷createPortal: ƒ createPortal$1(children, container)
 ▷findDOMNode: ƒ findDOMNode(componentOrElement)
 ▷flushSync: ƒ flushSync(fn, a)
 ▷hydrate: ƒ hydrate(element, container, callback)
 ▷render: ƒ render(element, container, callback)
 ▷unmountComponentAtNode: ƒ unmountComponentAtNode(container)
 ▷unstable_batchedUpdates: ƒ batchedUpdates$1(fn, a)
 ▷unstable_createPortal: ƒ unstable_createPortal(children, container)
 ▷unstable_renderSubtreeIntoContainer: ƒ renderSubtreeIntoContainer(parentComponent, element, containerNode, callback)
 ▷version: "17.0.1"
 ▷__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: {Events: Array(7)}
 ▷__proto__: Object

これらは、react-dom.development.jsの26280~26290行目でエクスポートされている。
https://github.com/wafuwafu13/react-17-react-dom-17/blob/master/react-dom.development.js#L26280

exports.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = Internals;
...
exports.render = render;
...

次回から、このrender関数について見ていこうと思う。

wafuwafu13.hatenadiary.com

Linuxを購入

のが届いた。Macの5倍厚い。1万5000円。Ubuntu 16.04。

前からこういう本をやりたかったけど、Macだと仮想環境を用意しないといけない。

その仮想環境がそもそもどう構築されているのか分からないし、慣れていない仮想環境で理解できる自信がなかったので、買った方が早いと思った。
あとなんとなくLinuxに慣れたいというのもあった。ついでにvimも。

上の本をやる前に、まずこれをやりはじめた。
図解がわかりやすく、コンピュータの中身を掴めた感じがした。

1/10追記
これをやり終えた。
脳内に散らばっていたネットワークの知識が集約されていく感じがした。
TCP/IP等の説明に抵抗感が全くなくなった。

この本も同時期に読んだのも、DNSという観点からネットワークを俯瞰することができて理解に拍車がかかった。

jQueryはいかにしてDOMを取得するか(3)

wafuwafu13.hatenadiary.com

いよいよ今回は、以下のHTMLを用意して実際にDOMを取得していく。

<!DOCTYPE html>
<html>
<head>
    <script src="https://code.jquery.com/jquery-3.5.1.js"></script>
</head>
<body>
<h1 id="hello">hello world</h1>
<script>
    console.log($('#hello'))
</script>
</body>
</html>

まずは前回と同じように、3133行目に定義されている関数の引数に何が入っているのかを調べる。
https://github.com/wafuwafu13/jquery-3.5.1/blob/master/jquery-3.5.1.js#L3133

jQuery.fn.init = function( selector, context, root ) {...

すると、selector"#hello"であり、contextrootundefinedであった。

よって、selectorはstring型であるため、3146行目の条件式はtrueになる。
https://github.com/wafuwafu13/jquery-3.5.1/blob/master/jquery-3.5.1.js#L3146

if ( typeof selector === "string" )

次の3147行目の条件式は、selector[ 0 ]#であるため、falseである。
https://github.com/wafuwafu13/jquery-3.5.1/blob/master/jquery-3.5.1.js#L3147

if ( selector[ 0 ] === "<" &&
...

続く3155行目で、変数matchに値が代入されている。
https://github.com/wafuwafu13/jquery-3.5.1/blob/master/jquery-3.5.1.js#L3155

match = rquickExpr.exec( selector );

ここで使われているrquickExprは、3131行目で定義されているRegExpである。
https://github.com/wafuwafu13/jquery-3.5.1/blob/master/jquery-3.5.1.js#L3131

rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,

そして、RegExp.prototype.exec()を用い、結果として以下のような配列を返し、変数matchに代入されている。

["#hello", undefined, "hello", index: 0, input: "#hello", groups: undefined]

よって、3159行目の条件式はtrueになる。
https://github.com/wafuwafu13/jquery-3.5.1/blob/master/jquery-3.5.1.js#L3159

if ( match && ( match[ 1 ] || !context ) ) {

match[1]undefinedであるため、3162行目の条件式はfalseである。
https://github.com/wafuwafu13/jquery-3.5.1/blob/master/jquery-3.5.1.js#L3162

if ( match[ 1 ] ) {

続く3192行目で、Document.getElementById()によりDOMが取得されている。
https://github.com/wafuwafu13/jquery-3.5.1/blob/master/jquery-3.5.1.js#L3192

elem = document.getElementById( match[ 2 ] );

document.getElementById("hello")の返り値はElementオブジェクトであり、以下のような値である。

h1#hello {align: "", title: "", lang: "", translate: true, dir: "", …},

続く3194行目からは、前回と同じ処理がなされている。
https://github.com/wafuwafu13/jquery-3.5.1/blob/master/jquery-3.5.1.js#L3194

if ( elem ) {

    // Inject the element directly into the jQuery object
    this[ 0 ] = elem;
    this.length = 1;
}
return this;

よって、console.log($('#hello'))で以下の値が得られた。

▽jQuery.fn.init [h1#hello]
 ▷0: h1#hello
    length: 1
 ▷__proto__: Object(0)

今回でjQueryはいかにしてDOMを取得するかが解明できた。
次は違うコードを読んでいきたい。

jQueryはいかにしてDOMを取得するか(2)

wafuwafu13.hatenadiary.com

前回は、以下のHTMLを用意し、

<!DOCTYPE html>
<html>
<head>
    <script src="https://code.jquery.com/jquery-3.5.1.js"></script>
</head>
<body>
<script>
    console.log($);
</script>
</body>
</html>

以下の実行結果を得た。

ƒ ( selector, context ) {

		// The jQuery object is actually just the init constructor 'enhanced'
		// Need init if jQuery is called (just allow error to be thrown if not included)
		return new jQuery…

これは、153行目で定義されている定数であった。
https://github.com/wafuwafu13/jquery-3.5.1/blob/master/jquery-3.5.1.js#L153

今回は、console.log($())とすることで、関数の実行結果、つまり157行目の、
https://github.com/wafuwafu13/jquery-3.5.1/blob/master/jquery-3.5.1.js#L157

jQuery.fn.init( selector, context )

の実行結果を見ていきたい。

関数は、3133行目に定義されている。
https://github.com/wafuwafu13/jquery-3.5.1/blob/master/jquery-3.5.1.js#L3133

jQuery.fn.init = function( selector, context, root ) {...

ここで、引数であるselectorcontextrootには何が入っているのかを調べると、
selectorは、

#document {location: Location, implementation: DOMImplementation, URL: "file:///Users/tagawahirotaka/Desktop/index.html",....

であり、contextrootundefinedであった。

よって、selectorは、Documentであることがわかる。

selectorが未定義ではなく、string型でもないため、3137行目と3146行目の条件分岐はスルーされる。
https://github.com/wafuwafu13/jquery-3.5.1/blob/master/jquery-3.5.1.js#L3137
https://github.com/wafuwafu13/jquery-3.5.1/blob/master/jquery-3.5.1.js#L3146

if ( !selector )
...
if ( typeof selector === "string" )


続く3214行目には以下のような条件分岐がある。
https://github.com/wafuwafu13/jquery-3.5.1/blob/master/jquery-3.5.1.js#L3214

else if ( selector.nodeType )

selector.nodeTypeを調べてみると、値は9であった。

DocumentNodeを継承しており、Node.nodeType9は、DOCUMENT_NODEを意味している。

この3215行目からの条件分岐内で、以下のような処理がなされる。
https://github.com/wafuwafu13/jquery-3.5.1/blob/master/jquery-3.5.1.js#L3215

this[ 0 ] = selector;
this.length = 1;
return this;

thisjQuery.fn.initのことで、そこにselectorが挿入されている。

しかし、console.log($())の出力を見てみると、

▽jQuery.fn.init {}
  ▷__proto__: Object(0)

となっているため、後続の処理で破棄されている。

ちなみに、thisjQuery.fn.initである理由は、生成されるインスタンス自身がthisにセットされるからである。

function foo() {
    console.log(this) // ▽ foo {}
                //  ▷ __proto__: Object
}

var bar = new foo()


wafuwafu13.hatenadiary.com