Reactのコードを読む(3)
前回はhello world
が描写されるところまでをみたので、今回はuseStateをみていこうと思う。
まずは、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) ...
つまり、useState
はReact
のプロパティとして定義されていて、コードでは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
のとき、つまりdispatcher
がnull
のとき生じる。
そのdispatcher
に代入されているReactCurrentDispatcher
は、115行目で定義されており、確かにReactCurrentDispatcher.current
はnull
である。
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
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行目のdispatchAction
をbindしたものが入っている。
これらの返り値を以下のように分割代入して用いる。
const [foo, setFoo] = useState(bar);
深追いできなかった部分は多かったが、以上でsetState
を読むのは終える。
ReactCurrentDispatcher
の更新が沼すぎるのでこれ以上Reactを読むかどうかは分からない、とても同じ人間が書いたとは思えない。
次回からはフロントの繋がりでBabelを読むか、Goのechoとかを読むかもしれない。
DOMの更新に関しては以下の記事がわかりやすかったのでこれを深めると良いと思っている。
Reactのコードを読む(2)
前回はReactDOM
についてみたので、今回はrender
でhello world
が描写されるところまでみていこうと思う。
まずは、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 ')); }
今回は引数のnode
がundefined
であるため、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.nodeTypeが1
ということはつまり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
は存在せず、root
はundefined
になる。
よって、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 world
がnodeValue
やtextContent
として含まれている。
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
などをみていきたい。
『リファクタリング(第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
superはECMAScript2015/ES6の仕様なので、tsconfig.jsonのtargetをes6
にしないといけなかった。
=============================
以降、読んだだけでは理解しづらかった箇所をメモ。
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.push
でcourses
が更新できてしまうと、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
関数について見ていこうと思う。
Linuxを購入
— わふわふ (@wafuwafu13_) 2020年11月22日
のが届いた。Macの5倍厚い。1万5000円。Ubuntu 16.04。
前からこういう本をやりたかったけど、Macだと仮想環境を用意しないといけない。
その仮想環境がそもそもどう構築されているのか分からないし、慣れていない仮想環境で理解できる自信がなかったので、買った方が早いと思った。
あとなんとなくLinuxに慣れたいというのもあった。ついでにvimも。
上の本をやる前に、まずこれをやりはじめた。
図解がわかりやすく、コンピュータの中身を掴めた感じがした。
1/10追記
これをやり終えた。
脳内に散らばっていたネットワークの知識が集約されていく感じがした。
TCP/IP等の説明に抵抗感が全くなくなった。
この本も同時期に読んだのも、DNSという観点からネットワークを俯瞰することができて理解に拍車がかかった。
jQueryはいかにしてDOMを取得するか(3)
いよいよ今回は、以下の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"
であり、context
、root
はundefined
であった。
よって、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)
前回は、以下の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 ) {...
ここで、引数であるselector
、context
、root
には何が入っているのかを調べると、
selector
は、
#document {location: Location, implementation: DOMImplementation, URL: "file:///Users/tagawahirotaka/Desktop/index.html",....
であり、context
、root
はundefined
であった。
よって、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
であった。
DocumentはNodeを継承しており、Node.nodeTypeの9
は、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;
this
はjQuery.fn.init
のことで、そこにselector
が挿入されている。
しかし、console.log($())
の出力を見てみると、
▽jQuery.fn.init {} ▷__proto__: Object(0)
となっているため、後続の処理で破棄されている。
ちなみに、this
がjQuery.fn.init
である理由は、生成されるインスタンス自身がthis
にセットされるからである。
function foo() { console.log(this) // ▽ foo {} // ▷ __proto__: Object } var bar = new foo()