Dash应用浏览器端回调常用方法总结

本文示例代码已上传至我的Github仓库https://github.com/CNFeffery/dash-master

大家好我是费老师,回调函数 是我们在Dash应用中实现各种交互功能的核心,在绝大多数情况下,我们只需要以纯Python的方式编写常规服务端回调函数即可,这也贯彻了Dash无需编写javascript即可构建web应用的理念。

但这并不代表在Dash应用中我们只能使用Python,更自由地,Dash针对回调函数编写还提供了client side callback (我们通常称作浏览器端回调 )相关功能,使得我们可以在仍然使用Python编排回调函数角色的基础上,嵌入自定义的javascript代码片段来执行相应的回调输入输出逻辑,从而解决一些特殊的需求。今天的文章中,我就将带大家一起学习Dash浏览器端回调常用的方法和技巧😎。
阅读本文大约需要15分钟

浏览器端回调 ,顾名思义,其对应的函数体计算过程是在每个用户的本地浏览器 中执行的,这在一些特殊的场景下,可以帮助我们节省服务器算力 、网络传输带宽 等消耗,还可以在用户网络状况很差 时,提升一些用户交互功能的流畅度 ,亦或是可以让我们在Dash应用中额外引入javascript生态的功能(譬如在Dash应用中高效渲染原生echarts图表)。

而在Dash中,我们主要有两种定义浏览器端回调的方式:

1 基于app.clientside_callback编写简单浏览器端逻辑

此种浏览器端回调定义方式适用于执行非常简单的javascript代码片段,只需要为app.clientside_callback()的第一个参数传入字符串形式的javascript函数体即可(推荐使用箭头函数),其中函数体内部参数的输入,以及结果的输出,原则类似常规的回调函数。

举个例子,我们来实现一段非常简单的逻辑,通过按钮的点击,来触发对应模态框的打开:

对应app.clientside_callback的完整应用代码如下:

app1.py

Python 复制代码
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

python 复制代码
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回调函数:

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

接着在相应的Python程序中配合ClientsideFunction按照下列格式关联编排回调函数即可:

python 复制代码
app.clientside_callback(
    ClientsideFunction(
        namespace='clientside',
        function_name='函数名称'
    ),
    # 照常编排回调角色
)

废话不多说,我们直接将上文中实时刷新系统时间的示例改造成ClientsideFunction形式以便理解:

assets/clientside_callbacks.js

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

app3.py

python 复制代码
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角色的浏览器端回调函数
  • callback-cs:ois:快速初始化具有InputOutputState角色的浏览器端回调函数

js文件中可用的快捷短语有:

  • callback:init:快捷生成浏览器端回调函数定义模板

3.2 常用对象在浏览器端回调中的写法

在常规的服务端回调函数中我们经常会使用到dash.no_updatePreventUpdatedash.callback_context等对象来辅助回调函数功能逻辑的完成,而在浏览器端回调中,这些对象的写法要做一定变化:

  • dash.no_update

dash.no_update在浏览器端回调中写作window.dash_clientside.no_update,你也可以用feffery-dash-snippets插件中的dash.no_update快捷短语生成:

  • PreventUpdate

PreventUpdate在浏览器端回调中写作PreventUpdate,你也可以用feffery-dash-snippets插件中的PreventUpdate快捷短语生成(注意,在浏览器端回调中throw window.dash_clientside.PreventUpdate等价于常规回调中的raise PreventUpdate):

  • dash.callback_context

dash.callback_context在浏览器端回调中写作window.dash_clientside.callback_context,你也可以用feffery-dash-snippets插件中的dash.callback_context快捷短语生成:

3.3 在浏览器端回调中返回组件元素

我们在常规回调函数中,经常会以一些组件的children或其他组件型 参数为Output目标,直接返回组件元素,在Python中这样做很稀疏平常,但是在浏览器端回调中,我们如果有此类需求,则需要返回规定的JSON数据格式,来表示一个组件元素:

json 复制代码
{
    props: {
        // 定义当前组件的各属性,如
        id: '组件id'
    },
    type: '组件完整名称,如AntdButton',
    namespace: '组件所属组件库完整名称,如feffery_antd_components'
}

我们还是结合实际案例来做演示,这里我们的演示功能实现了通过按钮点击触发新的消息提示弹出:

具体代码如下,可以看到只要我们按照格式返回相应的组件JSON数据,Dash就会在浏览器中自动进行转换及渲染:

app4.py

python 复制代码
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公众号。

相关推荐
databook13 小时前
Manim实现闪光轨迹特效
后端·python·动效
Juchecar14 小时前
解惑:NumPy 中 ndarray.ndim 到底是什么?
python
用户83562907805115 小时前
Python 删除 Excel 工作表中的空白行列
后端·python
Json_15 小时前
使用python-fastApi框架开发一个学校宿舍管理系统-前后端分离项目
后端·python·fastapi
数据智能老司机21 小时前
精通 Python 设计模式——分布式系统模式
python·设计模式·架构
数据智能老司机1 天前
精通 Python 设计模式——并发与异步模式
python·设计模式·编程语言
数据智能老司机1 天前
精通 Python 设计模式——测试模式
python·设计模式·架构
数据智能老司机1 天前
精通 Python 设计模式——性能模式
python·设计模式·架构
c8i1 天前
drf初步梳理
python·django
每日AI新事件1 天前
python的异步函数
python