Dash應用瀏覽器端回撥常用方法總結

費弗裡發表於2023-11-17

本文示例程式碼已上傳至我的Github倉庫https://github.com/CNFeffery/dash-master

大家好我是費老師,回撥函式是我們在Dash應用中實現各種互動功能的核心,在絕大多數情況下,我們只需要以純Python的方式編寫常規服務端回撥函式即可,這也貫徹了Dash無需編寫javascript即可構建web應用的理念。

但這並不代表在Dash應用中我們只能使用Python,更自由地,Dash針對回撥函式編寫還提供了client side callback(我們通常稱作瀏覽器端回撥)相關功能,使得我們可以在仍然使用Python編排回撥函式角色的基礎上,嵌入自定義的javascript程式碼片段來執行相應的回撥輸入輸出邏輯,從而解決一些特殊的需求。今天的文章中,我就將帶大家一起學習Dash瀏覽器端回撥常用的方法和技巧?。

Dash應用瀏覽器端回撥常用方法總結

閱讀本文大約需要15分鐘

瀏覽器端回撥,顧名思義,其對應的函式體計算過程是在每個使用者的本地瀏覽器中執行的,這在一些特殊的場景下,可以幫助我們節省伺服器算力、網路傳輸頻寬等消耗,還可以在使用者網路狀況很差時,提升一些使用者互動功能的流暢度,亦或是可以讓我們在Dash應用中額外引入javascript生態的功能(譬如在Dash應用中高效渲染原生echarts圖表)。

Dash應用瀏覽器端回撥常用方法總結

而在Dash中,我們主要有兩種定義瀏覽器端回撥的方式:

1 基於app.clientside_callback編寫簡單瀏覽器端邏輯

此種瀏覽器端回撥定義方式適用於執行非常簡單的javascript程式碼片段,只需要為app.clientside_callback()的第一個引數傳入字串形式的javascript函式體即可(推薦使用箭頭函式),其中函式體內部引數的輸入,以及結果的輸出,原則類似常規的回撥函式。

舉個例子,我們來實現一段非常簡單的邏輯,透過按鈕的點選,來觸發對應模態框的開啟:

Dash應用瀏覽器端回撥常用方法總結

對應app.clientside_callback的完整應用程式碼如下:

app1.py

import dash
from dash import html
import feffery_antd_components as fac
from dash.dependencies import Input, Output

app = dash.Dash(__name__)

app.layout = html.Div(
    [
        fac.AntdButton(
            '開啟模態框',
            id='open-modal',
            type='primary'
        ),
        fac.AntdModal(
            fac.AntdParagraph('測試內容'*100),
            id='modal',
            title='模態框示例'
        )
    ],
    style={
        'padding': '50px 100px'
    }
)

app.clientside_callback(
    '(nClicks) => true',
    Output('modal', 'visible'),
    Input('open-modal', 'nClicks'),
    prevent_initial_call=True
)

if __name__ == '__main__':
    app.run(debug=True)

可以看到,寫法非常簡單,對於編寫此類簡單瀏覽器端回撥的需求,我們只需要用到javascript最基礎的語法,非常的方便?,再來個稍微複雜一點的例子,我們基於輪詢元件,實現當前系統時間的實時更新:

app2.py

Dash應用瀏覽器端回撥常用方法總結
import dash
from dash import html, dcc
import feffery_antd_components as fac
from dash.dependencies import Input, Output

app = dash.Dash(__name__)

app.layout = html.Div(
    [
        dcc.Interval(
            id='interval',
            interval=1000  # 每秒觸發一次
        ),
        fac.AntdStatistic(
            id='current-datetime',
            title='當前時間'
        )
    ],
    style={
        'padding': '50px 100px'
    }
)

app.clientside_callback(
    '''(n_intervals) => {
        return `${new Date().toLocaleDateString().replaceAll("/", "-")} ${new Date().toLocaleTimeString()}`
    }''',
    Output('current-datetime', 'value'),
    Input('interval', 'n_intervals')
)

if __name__ == '__main__':
    app.run(debug=True)

2 基於ClientsideFunction編寫複雜瀏覽器端回撥

如果我們想要執行的瀏覽器端回撥邏輯比較複雜和冗長,那麼在app.clientside_callback裡用字串的方式寫大段的javascript程式碼就不太高效了?‍♂️,相應的我們可以改為使用ClientsideFunction來定義。

使用ClientsideFunction來定義瀏覽器端回撥,我們首先需要在我們的Dash應用靜態資源目錄下(預設為assets)建立相應的js檔案(名稱隨意,Dash應用會自動載入靜態資源目錄下的js檔案到使用者瀏覽器中),並在該js檔案中按照下列格式定義若干javascript回撥函式:

window.dash_clientside = Object.assign({}, window.dash_clientside, {
    clientside: {
        func1: () => {
            // write your code logic
        }
    }
});

接著在相應的Python程式中配合ClientsideFunction按照下列格式關聯編排回撥函式即可:

app.clientside_callback(
    ClientsideFunction(
        namespace='clientside',
        function_name='函式名稱'
    ),
    # 照常編排回撥角色
)

廢話不多說,我們直接將上文中實時重新整理系統時間的示例改造成ClientsideFunction形式以便理解:

assets/clientside_callbacks.js

window.dash_clientside = Object.assign({}, window.dash_clientside, {
    clientside: {
        update_datetime: (n_intervals) => {
            return `${new Date().toLocaleDateString().replaceAll("/", "-")} ${new Date().toLocaleTimeString()}`
        }
    }
});

app3.py

import dash
from dash import html, dcc
import feffery_antd_components as fac
from dash.dependencies import Input, Output, ClientsideFunction

app = dash.Dash(__name__)

app.layout = html.Div(
    [
        dcc.Interval(
            id='interval',
            interval=1000  # 每秒觸發一次
        ),
        fac.AntdStatistic(
            id='current-datetime',
            title='當前時間'
        )
    ],
    style={
        'padding': '50px 100px'
    }
)

app.clientside_callback(
    ClientsideFunction(
        namespace='clientside',
        function_name='update_datetime'
    ),
    Output('current-datetime', 'value'),
    Input('interval', 'n_intervals')
)

if __name__ == '__main__':
    app.run(debug=True)

這樣做的好處在於,我們可以把相對複雜的javascript邏輯在原生的js程式裡編寫,從而配合現代化ide獲得更高效的程式設計體驗,並且利用ClientsideFunction形式,可以很方便地實現外部js框架的引入使用,譬如引入使用原生echarts,篇幅有限,今天先按下不表,之後另外發文舉例介紹。

3 編寫瀏覽器端回撥的常用技巧

透過上文,我們知曉了Dash中構建瀏覽器端回撥的基本形式,下面我們補充一些有關瀏覽器端回撥的實用技巧:

3.1 配合外掛快捷生成模板程式碼

編寫瀏覽器端回撥,尤其是配合ClientsideFunction時,其程式碼格式還是有些特殊的,不過別擔心,如果你恰好在使用vscode編寫Dash應用,可以在擴充裡安裝由我開發維護的外掛feffery-dash-snippets,安裝完成後,可以透過輸入一些快捷短語,進行相關程式碼模板的生成。

目前針對瀏覽器端回撥+ClientsideFunction,在py檔案中可用的快捷短語有:

  • callback-cs:oi:快速初始化具有InputOutput角色的瀏覽器端回撥函式
Dash應用瀏覽器端回撥常用方法總結
  • callback-cs:ois:快速初始化具有InputOutputState角色的瀏覽器端回撥函式
Dash應用瀏覽器端回撥常用方法總結

js檔案中可用的快捷短語有:

  • callback:init:快捷生成瀏覽器端回撥函式定義模板
Dash應用瀏覽器端回撥常用方法總結

3.2 常用物件在瀏覽器端回撥中的寫法

在常規的服務端回撥函式中我們經常會使用到dash.no_updatePreventUpdatedash.callback_context等物件來輔助回撥函式功能邏輯的完成,而在瀏覽器端回撥中,這些物件的寫法要做一定變化:

  • dash.no_update

dash.no_update在瀏覽器端回撥中寫作window.dash_clientside.no_update,你也可以用feffery-dash-snippets外掛中的dash.no_update快捷短語生成:

Dash應用瀏覽器端回撥常用方法總結
  • PreventUpdate

PreventUpdate在瀏覽器端回撥中寫作PreventUpdate,你也可以用feffery-dash-snippets外掛中的PreventUpdate快捷短語生成(注意,在瀏覽器端回撥中throw window.dash_clientside.PreventUpdate等價於常規回撥中的raise PreventUpdate):

Dash應用瀏覽器端回撥常用方法總結
  • dash.callback_context

dash.callback_context在瀏覽器端回撥中寫作window.dash_clientside.callback_context,你也可以用feffery-dash-snippets外掛中的dash.callback_context快捷短語生成:

Dash應用瀏覽器端回撥常用方法總結

3.3 在瀏覽器端回撥中返回元件元素

我們在常規回撥函式中,經常會以一些元件的children或其他元件型引數為Output目標,直接返回元件元素,在Python中這樣做很稀疏平常,但是在瀏覽器端回撥中,我們如果有此類需求,則需要返回規定的JSON資料格式,來表示一個元件元素:

{
    props: {
        // 定義當前元件的各屬性,如
        id: '元件id'
    },
    type: '元件完整名稱,如AntdButton',
    namespace: '元件所屬元件庫完整名稱,如feffery_antd_components'
}

我們還是結合實際案例來做演示,這裡我們的演示功能實現了透過按鈕點選觸發新的訊息提示彈出:

Dash應用瀏覽器端回撥常用方法總結

具體程式碼如下,可以看到只要我們按照格式返回相應的元件JSON資料,Dash就會在瀏覽器中自動進行轉換及渲染:

app4.py

import dash
from dash import html
import feffery_antd_components as fac
from dash.dependencies import Input, Output

app = dash.Dash(__name__)

app.layout = html.Div(
    [
        fac.AntdButton(
            '新的訊息',
            id='new-message',
            type='primary'
        ),
        html.Div(id='new-message-container')
    ],
    style={
        'padding': '50px 100px'
    }
)

app.clientside_callback(
    '''(nClicks) => ({
        props: {
            content: "新的訊息,nClicks:" + nClicks,
            type: "info"
        },
        type: "AntdMessage",
        namespace: "feffery_antd_components"
    })''',
    Output('new-message-container', 'children'),
    Input('new-message', 'nClicks'),
    prevent_initial_call=True
)

if __name__ == '__main__':
    app.run(debug=True)

以上就是本文的全部內容,更多有關dash應用開發的前沿知識和技巧歡迎持續關注玩轉dash公眾號。

相關文章