主要想要知道:
- 什麼時候會發出 request?re-render 就會發嗎?
- data 什麼時候會更新?
3.4.10 (支援 React 18 concurrent 前)
useQuery
Pseudo Code:
function useQuery(query, options) {const context = useContext(getApolloContext());const [tick, setTick] = useState(0);const forceUpdate = () => {setTick((t) => t + 1);};const updatedOptions = options ? { ...options, query } : { query };const queryDataRef = useRef();const queryData =queryDataRef.current ||(queryDataRef.current = new QueryData({options: updatedOptions,context,onNewData() {// sometimes delayed into micro task queue actuallyforceUpdate();},}));queryData.setOptions(updatedOptions);queryData.context = context;const resultRef = useRef();const resultDependenciesRef = useRef();const prevRestulDependencies = resultDependenciesRef.current;const currentResultDependencies = {options: _.omit(updatedOptions, ["onError", "onComplete"]),context,tick,};// `equal` from `@wry/equality`:// https://github.com/benjamn/wryware/blob/main/packages/equality/src/tests.tsif (!equal(prevResultDependencies, currentResultDependencies)) {resultRef.current = lazy ? queryData.executeLazy() : queryData.execute();}// Clear queryData when unmountinguseEffect(() => {return () => {queryData.cleanup();queryDataRef.current = void 0;};}, []);// afterExecuteuseEffect(() => queryData.afterExecute({ lazy }),[queryResult.loading,queryResult.networkStatus,queryResult.error,queryResult.data,queryData.currentObservable,],);return result;}
幾個要點:
-
只有第一次 render 會做的事:
- render
new QueryData()
- render
-
每次 render 會做的事:
- render
- 當
options
,context
,tick
和前次 render 不同時 ->result = lazy ? queryData.executeLazy() : queryData.execute()
- 當
- effects
- 當
{ loading, networkStatus, error, data } = result
或queryData.currentObservable
和前次 render 不同時 -> 執行queryData.afterExecute({ lazy })
- 當
- render
-
unmount 前會做的事:
queryData.cleanup();
和queryDataRef.current = void 0;
-
QueryData
instance 的onNewData
觸發時會做的事:setState(x => x+1)
觸發 re-render,下次 render 會執行queryData.execute()
QueryData
constructor
super(options, context);
this.options = options || {};
this.context = context || {};
this.onNewData = onNewData;
queryData.execute()
this.refreshClient();
this.client
指到現在 options 或 context 裡的client
this.updateObservableQuery();
- initiate observableQuery if needed:
this.currentObservable = this.refreshClient().client.watchQuery({...observableQueryOptions,});
queryManager.watchQuery()
new QueryInfo(this)
new ObservableQuery({ queryManager: this, queryInfo, options })
queryInfo.init({ document: options.query, observableQuery: observable, variables: options.variables })
- update observableQuery options:
if (!equal(newObservableQueryOptions, this.previous.observableQueryOptions)) {this.currentObservable.setOptions(newObservableQueryOptions);}
- 會觸發 reobserve 可能 refetch
- initiate observableQuery if needed:
reutrn this.getExecuteResult()
- 就只是拿
this.currentObservable.getCurrentResult()
- 就只是拿
queryData.afterExecute()
this.startQuerySubscription();
-
Setup a subscription to watch for Apollo Client
ObservableQuery
changes. When new data is received, and it doesn’t match the data that was used during the lastQueryData.execute
call (and ultimately the last query component render), trigger theonNewData
callback. If not specified,onNewData
will fallback to the defaultQueryData.onNewData
function (which usually leads to a query component re-render). this.currentObservable!.subscribe({ next, error })
- 會觸發
currentObservable
的 subscriber 如果是第一個 observer 會進而觸發 reobserve 打 HTTP request
- 會觸發
-
this.handleErrorOrCompleted();
3.6.8 (支援 React 18 concurrent 後)
Psudo code: 3.6.8
function useQuery(options) {const [_tick, setTick] = useState(0);const forceUpdate = () => {setTick((tick) => tick + 1);};const resultRef = useRef();actutally;const obsQuery = client.watchQuery(options);const subscribe = useCallback(() => {const onNext = () => {const nextResult = obsQuery.getCurrentResult();if (!isSame(result, nextResult)) {resultRef.current = nextResult;forceUpdate();}};const onError = (error) => {if (!isSame(result.error, error)) {resultRef.current = { ...resultRef.current, error, loading: false };forceUpdate();}};const subscription = obsQuery.subscribe(onNext, onError);return () => {subscription.unsubscribe();};},[/* dependencies */],);const result = useSyncExternalStore(subscribe,() => resultRef.current,() => resultRef.current,);return result;}
Source: useQuery.ts
幾個要點:
apollo-client-react
自己 implementuseSyncExternalStore
。實作大致跟 React 官方的 use-sync-external-store shim 相同。差在檢查 snapshot 變化是用===
,React 是用Object.is