阿川私房教材:
學 JavaScript 前端,帶作品集去面試!

63 個專案實戰,寫出作品集,讓面試官眼前一亮!

立即開始免費試讀!

最近,我有一個任務是學習一個我從未使用過或見過相關內容的新工具,我想:為什麼不寫下我如何學習它的過程呢?

這就是一篇從不同視角教你學習事物的文章。雖然不一定是最好的方法,但你可以重用這裡寫的一些概念。

內容表

1. 序言

圖片描述

最近我和朋友開始了一個新專案(又一個糟糕的虛擬軟體即服務應用)使用 PHP 和 Laravel。專案本身並不重要,但 技術棧 是關鍵。為什麼呢?因為我們 沒有足夠的財力去花錢在自架的基礎設施上,也沒有預算購買良好的付費工具……所以這點很重要。

因此,如果我們要構建一些具有任何整合功能的東西,它至少需要有一個免費層可以使用,且不能是 實作起來讓人抓狂 的工具。在思考這一點時,我們困惑了:我們要怎麼進行使用者的 追蹤/指標 呢?

我的目標是 展示每頁 SaaS 使用者的分析。普通開發者可能會想:我們可以使用 Google AnalyticsGoogle Tag Manager,對吧?好吧……讓我告訴你查斯特曾經說過的一句話:

我努力了這麼久,卻沒什麼用,最後一切似乎都不重要 - 查斯特·貝寧頓

老兄,我一直是個 Google UI 反感者。但這次不是為了工作,而是為了我自己,而我 沒有多餘的時間。所以,我花了 4 小時嘗試……在我的專案上設置基本配置並理解如何創建一個簡單的標籤?最後我 浪費了時間,因為什麼都沒有運作。

像是,為什麼所有的 Google 產品都有一個你需要花費幾乎一個博士學位才能使用的介面?到處都是太多的資訊!

就在這時,我的朋友告訴我關於 PostHog,一個開源的分析平台,它讓你可以使用自架或雲端選項追蹤用戶行為(而在實作上超越 GTM/GA4)。但同時,我希望你能耐心看到這篇文章的結尾,因為這項研究花了我一些小時和幾行程式碼。

2. 基礎

圖片描述

好吧,這是最明顯的事情,但必須說明:當你嘗試一個新工具時,你必須玩弄它的各個方面。我知道我們是開發者,但 產品的 UI 是在試圖告訴你一些東西

2.1 當你不懂時就去玩弄它

這個 PostHog 的過程花了 無限的時間來設置,這無疑是一個優點。然而,UI 上發生了太多事情,我的第一個想法是嘗試 點擊所有東西在紀錄時間內摧毀我正在做的專案

想想看:如果你只因為在雲端或「生產」環境中而害怕使用一个平台/產品,那麼最後你什麼也學不會。

在這一步結束時,我只刪除了這個毀損的專案,並重新開始一個新的。但在這一點上,我已經知道了 UI 的運作方式。

警告: 別在你公司雲端帳號中進行這樣的操作,建一個獨立的帳號並在自己的環境中嘗試!!

2.2 但是……這並不那麼明顯

圖片描述

好吧……我必須坦誠告訴你,有時候摧毀一切並不是答案,因為這甚至不是個問題 這取決於你正在使用的功能。在 PostHog,他們擁有 「TrendsQuery」、「RetentionQuery」、「Web Vitals」 和對於每種類型查詢非常 華麗的 UI 查詢生成器。非常令人印象深刻!但是……我無法立即使用它。

由於我有一種叫做 ADHD 的驚人狀況,當我看到最小的 包含條件元件的複雜 UI 出現在螢幕上時,我會 完全崩潰並開始恐慌。但這不應該是放棄的理由,對吧?

這部分並不是對產品的批評,而是我想傾訴的心聲。數據科學是一個無限條件規則的領域,擁有越多的資訊,其準確性就會越高,而我正在邁出學習的第一步。

3. 文件

圖片描述

當然,文件!我為什麼之前不研究這個呢?我是說,這是開發者的天地,對吧?對!但我們是 將使用產品的開發者,所以第一步是必須的。

我通常不會看教程,儘管我知道每個人都應該。我只是不喜歡,對我來說 好的 API 參考必須告訴你所有事情,但在我看來所有事情會是什麼呢?

  • 「可索引」標題: 在目錄下,這會很有幫助。
  • 精煉的描述: 告訴你在初次使用時需要知道的一切。
  • 實作範例: 使用 PHP、JS、Go、Rust,主要是 cURL
  • 預期回應: 根據響應狀態(200、401、422、500)將返回的有效載荷。
  • 實作參考: 可以找到實際代碼以閱讀和理解客戶端/伺服器端如何操作。
  • 所需範圍: 通常你應該為端點設置範圍。

在大多數情況下,你會看到許多像這樣的元素,但這取決於你所使用的 API。以我這次使用 PostHog 特定實作 為例,挑戰在於 /query/create 端點具有 多型請求和響應,因此並沒有響應有效載荷。

所以,這時我們開始深入研究實際的代碼,因為我們需要理解這些端點如何與實際的實作一起運作。

我本可以只是寫下 HogQL 查詢,但對我來說,學這個會花費更多時間。

4. 原始碼

圖片描述

到這個時候我們知道我們想要什麼,對吧?我們想了解每一種類型請求所傳送的有效載荷。在我這個案例中,我想理解 Retention 和 Trends Query 在 PostHog 上如何運作。由於該產品是開源的,我可以直接從 GitHub 上閱讀它!

前端是用 TypeScript 寫的,後端則是用 Python 寫的。因為我是後端開發者,你可能會認為我會選擇 Python 實作來深入探索,對吧?其實,我因為很多原因一直討厭 Python。此外,TypeScript 提供了我可以用作代碼搜尋參考的類型安全。

TypeScript 原始碼 中,我找到了客戶端所需的一切實作,但看到之後,我直奔進入了兔子洞,你會明白為什麼。

看這段程式碼(參考)

export interface TrendsQuery extends InsightsQueryBase<TrendsQueryResponse> {
    kind: NodeKind.TrendsQuery
    /**
     * 回應的細分程度。可以是 `hour`、`day`、`week` 或 `month`
     *
     * @default day
     */
    interval?: IntervalType
    /** 要包含的事件和行動 */
    series: AnyEntityNode[]
    /** 特定於趨勢分析的屬性 */
    trendsFilter?: TrendsFilter
    /** 事件和行動的細分 */
    breakdownFilter?: BreakdownFilter
}

在這裡我們可以假設幾個事情:

  • 有一個繼承自 InsightsQueryBase 的類別,因此其他查詢擁有相同的基礎。
  • TrendsQueryResponse 是可用的並必須遵循某種模式,因此我們可以稍後再檢查。
  • 查詢的 「關聯」(你可以將其視為一種 過濾器)不太明確,因此需要更多研究。

首先,我們需要知道基礎是什麼,並尋找 InsightsQuery 關聯

/** Insight 查詢節點的基類。不得直接使用。 */
export interface InsightsQueryBase<R extends AnalyticsQueryResponseBase<any>> extends Node<R> {
    /** 查詢日期範圍 */
    dateRange?: DateRange
    /**
     * 通過應用相應的過濾器來排除內部和測試用戶
     *
     * @default false
     */
    filterTestAccounts?: boolean
    /**
     * 針對所有系列的屬性過濾器
     *
     * @default []
     */
    properties?: AnyPropertyFilter[] | PropertyGroupFilter
    /**
     * 群組彙總
     */
    aggregation_group_type_index?: integer | null
    /** 抽樣率 */
    samplingFactor?: number | null
    /** Insight 的可視化中使用的顏色 */
    dataColorTheme?: number | null
    /** 執行查詢時使用的修飾符 */
    modifiers?: HogQLQueryModifiers
}

然後你會明白你正在 挖掘一個兔子洞,因為這些 關聯 看起來是巨大 物件,賦予 API 不同的行為

經過幾小時的學習,我開始認識到這些關聯如何影響查詢,像是:

  • 細分: 更像是一個 GroupBy 子句
  • 屬性: 多型過濾,完全取決於查詢
  • 系列: 正在檢索的數據類型(例如,「觀看數」)
  • 間隔: 一個從/到的日期過濾。

這只是 冰山一角。圍繞 PostHog 的許多請求還有很多其他關聯。但這裡問題是:

  • 如果有一個 InsightsQueryBase,那麼這些關聯是否會與該類別的其他類型的查詢 共享
  • 是否有其他類型的基類查詢可以有相同的關聯?
  • 我是否應該理解每種類型的 關聯如何影響最終查詢

我是說,我們必須理解。也許不需要那麼深入,但至少要有個大概了解。但如果文件未提供例子,我們如何取得證據?

好吧,那就是我解釋我最喜歡的瀏覽器功能:網路標籤!

5. 網路標籤

圖片描述

我剛學會了如何 「探索」 從瀏覽器直接訪問網路應用的過程,通過對端點進行 AB 測試。你可能會想:

「根據服務的不同,你無法建立一個自用機器人,因為這可能違反服務條款」 - 普通用戶

我會回答說 我一點也不在乎,因為我這樣做是為了 學習目的。如果我這樣做,那是為了 理解事物的運作方式建立自己的工具使用他們的產品

隨便說一下:我自 2014 年以來就已經在檢查 元素/請求。我第一個 「真實」的 GitHub 專案基本上就是檢查一個名為「TribalWars」的瀏覽器遊戲中的所有 請求,並構建一個 CLI 應用/機器人,目標是通過終端玩這個遊戲,最終目的是不再玩它(建立一個自用機器人帳號)。

圖片描述

回到 PostHog 的實作,當我開始檢查時,我發現每種類型查詢有 不同的有效載荷。有了這些,我可以做出 更多假設,並用它們進行下一步。

{
    "client_query_id": "54f620e7-fdfe-4749-af41-caed0a3fe671",
    "query": {
        "breakdownFilter": {
            "breakdown": "$geoip_country_code",
            "breakdown_type": "event"
        },
        "conversionGoal": null,
        "dateRange": {
            "date_from": "-7d",
            "date_to": null
        },
        "filterTestAccounts": false,
        "kind": "TrendsQuery",
        "properties": [],
        "series": [
            {
                "event": "$pageview",
                "kind": "EventsNode",
                "math": "dau",
                "name": "Pageview"
            }
        ],
        "trendsFilter": {
            "display": "WorldMap"
        }
    },
    "refresh": "async"
}

還有第二個查詢以防萬一 :p

{
    "client_query_id": "25d4b82f-5bbe-4665-89a7-7aedcc7b103e",
    "query": {
        "dateRange": {
            "date_from": "-7d",
            "date_to": null
        },
        "filterTestAccounts": false,
        "kind": "RetentionQuery",
        "properties": [],
        "retentionFilter": {
            "period": "Week",
            "retentionReference": "total",
            "retentionType": "retention_first_time",
            "totalIntervals": 8
        }
    },
    "refresh": "async"
}

我本來會包含結果查詢,但那會太多代碼,這部分的重點已經說明清楚了。現在是時候結束整個流程了。

6. 實作

圖片描述

這時候你開始思考,「可用的工具是否符合我的需求?如果 SDK 沒有提供完全類型的 API,我寧願自己建立東西。我是說,我喜歡從 零開始 做事情!是的,就是那種重新發明輪子的尿性,你知道的?

原因很簡單:我是個通過重建事物並觀察 它們的行為 來學習的人。而且因此,我決定使用 PHP 建立一個 PostHog 專用查詢生成器,以了解事物如何在我這邊運作。

到目前為止,我得到的結果是:

  • 我們有多種類型的查詢;
  • 這些查詢可以繼承屬性/關聯,但不一定要使用所有這些屬性;
  • 查詢共享過濾/關聯;

那麼,我們該如何組織呢?經過數小時在 原始碼網路 標籤的挖掘後,這是我的答案:

------
- 查詢生成器功能在我自己的 PostHogSDK 中
------

查詢
├── 生成器
│   ├── AbstractQueryBuilder.php
│   └── Insights
│       ├── AbstractInsightsQueryBuilder.php
│       ├── RetentionQueryBuilder.php
│       └── TrendsQueryBuilder.php
├── 過濾器
│   ├── 細分
│   │   ├── BreakdownFilter.php
│   │   ├── BreakdownTypeEnum.php
│   │   └── 關聯
│   │       └── InteractsWithBreakdown.php
│   ├── 比較
│   │   ├── CompareFilter.php
│   │   └── 關聯
│   │       └── InteractsWithCompare.php
│   ├── 轉換目標
│   │   ├── ActionConversionGoal.php
│   │   ├── 關聯
│   │   │   └── InteractsWithConversionGoal.php
│   │   ├── 合約
│   │   │   └── ConversionGoalContract.php
│   │   └── CustomEventConversionGoal.php
│   ├── 日期範圍
│   │   ├── 關聯
│   │   │   └── InteractsWithDateRange.php
│   │   └── DateRangeFilter.php
│   ├── 間隔
│   │   ├── 關聯
│   │   │   └── InteractsWithInterval.php
│   │   └── QueryIntervalEnum.php
│   ├── 節點
│   │   ├── ActionNode.php
│   │   ├── 關聯
│   │   │   ├── InteractsWithMath.php
│   │   │   └── InteractsWithSeries.php
│   │   ├── 合約
│   │   │   └── EntityNodeContract.php
│   │   ├── EntityNodeKindEnum.php
│   │   ├── EntityNode.php
│   │   ├── EventsNode.php
│   │   └── MathEnum.php
│   ├── 屬性
│   │   ├── BaseProperty.php
│   │   ├── 關聯
│   │   │   └── InteractsWithProperties.php
│   │   ├── 合約
│   │   │   └── PropertyFilterContract.php
│   │   ├── 過濾
│   │   │   ├── EventPropertyFilter.php
│   │   │   └── SessionPropertyFilter.php
│   │   ├── PropertyFilterKind.php
│   │   └── PropertyOperator.php
│   └── 保留
│       ├── 關聯
│       │   └── InteractsWithRetention.php
│       ├── 列舉
│       │   ├── RetentionPeriodEnum.php
│       │   ├── RetentionReferenceEnum.php
│       │   └── RetentionTypeEnum.php
│       └── RetentionFilter.php
├── QueryBuilderInterface.php
├── QueryFactory.php
└── QueryKindEnum.php

每個過濾器都有自己的專屬位置,可以使用 PHP Traits 繼承到任何類型的生成器:

trait InteractsWithDateRange  
{  
    protected ?DateRangeFilter $dateRange = null;  
    public function setDateRange(DateRangeFilter $dateRange): self  
    {  
        $this->dateRange = $dateRange;  

        return $this;  
    }    
    public function getDateRange(): ?DateRangeFilter  
    {  
        return $this->dateRange;  
    }  

    private function buildDateRange(array &$payload): void  
    {  
        if ($this->dateRange !== null) {  
            $payload['dateRange'] = [  
                'date_from' => $this->dateRange->from,  
                'date_to' => $this->dateRange->to,  
            ];        
        }    
    }
}

所以查詢生成器的唯一要求是必須擁有我提供的 "build()" 方法,這樣我可以確保最終類型將得到滿足。下面的每次使用是不同的通用 QueryBuilder 關聯。

class TrendsQueryBuilder extends AbstractInsightsQueryBuilder  
{  
    use InteractsWithInterval,  
        InteractsWithDateRange,  
        InteractsWithBreakdown,  
        InteractsWithCompare,  
        InteractsWithConversionGoal,  
        InteractsWithProperties,  
        InteractsWithSeries;  

    protected QueryKindEnum $queryType = QueryKindEnum::TrendsQuery;  

    public static function make(): self  
    {  
        return new self();  
    }  

    public function build(): array  
    {  
        return $this->jsonSerialize();  
    }  

    public function jsonSerialize(): array  
    {  
        $payload = parent::jsonSerialize();  

        $payload['kind'] = $this->queryType->value;  

        $this->buildDateRange($payload);  
        $this->buildInterval($payload);  
        $this->buildSeries($payload);  
        $this->buildCompare($payload);  
        $this->buildProperties($payload);  
        $this->buildConversionGoal($payload);  
        $this->buildBreakdown($payload);  

        return $payload;  
    }
}

在編碼時,我們必須特別注意實作過程中的微小細節。坦率地說,當我建立這種工具時,我通常會想,有人會使用它,這會更提升我的關注度。

這項研究的最終成果是針對 PostHog API 的完整 TrendsRetention QueryBuilder


test('能夠構建保留查詢', function () {  
    $expected = [  
        "kind" => "RetentionQuery",  
        "dateRange" => [  
            "date_from" => "-7d",  
            "date_to" => null  
        ],  
        "filterTestAccounts" => false,  
        "retentionFilter" => [  
            "retentionType" => "retention_first_time",  
            "retentionReference" => "total",  
            "totalIntervals" => 8,  
            "period" => "Week"  
        ]  
    ];  
    $queryBuilder = RetentionQueryBuilder::make()  
        ->setDateRange(DateRangeFilter::from('-7d'))  
        ->setRetention(RetentionFilter::weekly()  
            ->setRetentionType(RetentionTypeEnum::FIRST_TIME)  
            ->setRetentionReference(RetentionReferenceEnum::Total)  
        );  
    expect($queryBuilder)->toBeInstanceOf(RetentionQueryBuilder::class)  
        ->and($queryBuilder->build())->toMatchArray($expected);  
});

test('能夠構建趨勢查詢', function () {  
    $expected = [  
        'kind' => 'TrendsQuery',  
        'filterTestAccounts' => false,  
        'dateRange' => [  
            'date_from' => '-7d',  
            'date_to' => null,

        ],        
        'interval' => 'day',  
        'series' => [  
            [                
                'event' => '$pageview',  
                'kind' => 'EventsNode',  
                'math' => 'dau',  
                'name' => 'Pageview',  
            ],        
        ],        
            'compareFilter' => [  
            'compare' => true,  
        ],        
        'properties' => [  
            [                'type' => 'event',  
                'key' => '$host',  
                'operator' => 'exact',  
                'value' => 'api-main-ofjibb.laravel.cloud',  
            ],        
        ],    
    ];  
    $actual = TrendsQueryBuilder::make()  
        ->setDateRange(DateRangeFilter::from('-7d'))  
        ->setInterval(QueryIntervalEnum::Day)  
        ->addCompareFilter()  
        ->addSeries(EventsNode::make('$pageview', MathEnum::DAU, 'Pageview'))  
        ->addProperty(EventPropertyFilter::make('$host', PropertyOperator::Exact, 'api-main-ofjibb.laravel.cloud'));  

    expect($actual)->toBeInstanceOf(TrendsQueryBuilder::class)  
        ->and($actual->build())->toMatchArray($expected);  
});

你可以透過 這裡點擊 查看此實作的 Pull Request。也歡迎你為這個專案做出貢獻!

7. 結論

這只是一個我 非常享受並決定寫下來的 PostHog API 的 學習研究。從 零開始構建事物和做適當的研究 是幫助你在 另一個層面上學習事物 的好方法。

現在我可以告訴你,我可以以 非常流暢的方式 使用 PostHog 平台,不再擔心這篇文章第一部分提到的問題,因為 我只摧毀過一次,分開了關聯,並分別研究了每一個

這種方法——摧毀事物深入文檔探索網路標籤從頭重建——可以應用於 任何技術。無論是 PostHog、新的 API,還是某個框架,理解內部邏輯使你成為一個更好的開發者

就是這樣!希望你喜歡,別忘了喝水!


原文出處:https://dev.to/danielhe4rt/how-i-learn-any-type-of-new-technology-as-a-senior-developer-47lj


共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。

阿川私房教材:
學 JavaScript 前端,帶作品集去面試!

63 個專案實戰,寫出作品集,讓面試官眼前一亮!

立即開始免費試讀!