Google Apps Script更新PropertiesService后跨卡片数据不刷新问题
如何更新
PropertiesService中的数据并在其他Card中展示 开发Google Calendar插件时出现数据陈旧、卡片/UI始终显示旧值的问题,复现场景如下:
- 创建Google Calendar插件项目,在
appsscript.json中配置首页构建触发器:
// appsscript.json // set the trigger to build the home .... .... "addOns": { "calendar": { "homepageTrigger": { "enabled": true, "runFunction": "buildInitState" } }, "common": { .... ....
- 编写初始化函数构建首页卡片,初始实现代码:
const userStore = PropertiesService.getUserProperties(); // setup userStore and add default values for date userStore.setProperties({ date: (new Date()).getTime(), }); const showSettingsPageAction = CardService.newAction().setFunctionName('showSettingsPage'); const handleDateChangeAction = CardService.newAction().setFunctionName('handleDateChange'); function handleDateChange(e) { //change the date to what user selects const newDate = (new Date(e.formInput.date_field.msSinceEpoch)).getTime(); userStore.setProperty('date', newDate); } // Homepage card function buildInitState(e) { const mainCard = CardService.newCardBuilder(); // add a date picker const DatePicker = CardService.newDatePicker() .setTitle('set date') .setFieldName('date_field') .setValueInMsSinceEpoch((new Date()).getTime()) .setOnChangeAction( CardService.newAction().setFunctionName('handleDateChangeAction') ); // add a button to nav to another card const ButtonFindSlots = CardService.newTextButton() .setText('NAV TO SETTINGS CARD') .setOnClickAction(showSettingsPageAction); mainCard .addSection(DatePicker) .addSection(ButtonFindSlots) mainCard.build(); }
- 编写
showSettingsPage函数构建设置页卡片,初始实现代码:
function showSettingsPage() { const card = CardService.newCardBuilder(); const cardSection = CardService.newCardSection(); let content = userStore.getProperty('date'); let p = CardService.newTextParagraph().setText(content); card.addSection(cardSection); return card.build(); }
测试时用户在首页修改日期后导航到设置页,日期始终显示初始默认值,无法确认是数据未更新还是卡片未刷新。
问题原因与修复方案
代码存在4个核心错误,按优先级排序:
- 回调函数名绑定错误:日期选择器的
onChangeAction绑定的函数名为handleDateChangeAction,但实际实现的存值函数名为handleDateChange,修改日期的回调从未触发,值根本没有写入PropertiesService。 - 全局逻辑污染:将
userStore实例定义在全局作用域,且在全局作用域直接执行setProperties写入默认日期。Google Apps Script运行时会缓存全局上下文,既会导致读取到旧的属性实例,也会在每次脚本运行时覆盖用户已经保存的自定义日期。 - Action回调无合法响应:即使回调正常触发,修改完属性后如果不返回
ActionResponse通知UI更新,插件前端不会同步状态,后续导航可能拿到缓存的旧卡片状态。 - 组件未正确挂载:
showSettingsPage函数中创建了显示日期的文本段落,但未将其添加到卡片分区中,即使读到正确值也无法在页面渲染。
修复后的完整可运行代码如下:
// 日期修改回调 function handleDateChange(e) { // 每次执行时重新获取属性存储实例,禁止使用全局缓存的实例 const userStore = PropertiesService.getUserProperties(); const newDate = e.formInput.date_field.msSinceEpoch; userStore.setProperty('date', newDate); // 返回ActionResponse,通知UI更新当前首页卡片 return CardService.newActionResponseBuilder() .setNavigation(CardService.newNavigation().updateCard(buildInitState(e))) .build(); } // 首页构建函数 function buildInitState(e) { const userStore = PropertiesService.getUserProperties(); // 仅当date属性不存在时才写入默认值,避免覆盖用户修改的内容 if (!userStore.getProperty('date')) { userStore.setProperty('date', new Date().getTime()); } // 读取存储中最新的日期值 const savedDate = Number(userStore.getProperty('date')); // 定义页面跳转Action const showSettingsPageAction = CardService.newAction().setFunctionName('showSettingsPage'); const mainCard = CardService.newCardBuilder(); // 日期选择器默认值取存储中的最新值,不要每次初始化都用当前时间 const datePicker = CardService.newDatePicker() .setTitle('set date') .setFieldName('date_field') .setValueInMsSinceEpoch(savedDate) // 绑定正确的回调函数名 .setOnChangeAction(CardService.newAction().setFunctionName('handleDateChange')); const navSettingsBtn = CardService.newTextButton() .setText('NAV TO SETTINGS CARD') .setOnClickAction(showSettingsPageAction); // 组件需要先加到Section,再把Section加到Card const dateSection = CardService.newCardSection().addWidget(datePicker); const btnSection = CardService.newCardSection().addWidget(navSettingsBtn); return mainCard.addSection(dateSection).addSection(btnSection).build(); } // 设置页构建函数 function showSettingsPage() { // 每次进入页面时重新获取属性实例、读取最新值 const userStore = PropertiesService.getUserProperties(); const card = CardService.newCardBuilder(); const cardSection = CardService.newCardSection(); const savedDate = userStore.getProperty('date'); const textP = CardService.newTextParagraph().setText(`存储的日期时间戳:${savedDate}`); // 把文本组件添加到分区中 cardSection.addWidget(textP); return card.addSection(cardSection).build(); }
开发时需要遵守三个规则避免同类问题:
- 所有和
PropertiesService交互的逻辑,不要把实例、属性值存在全局变量中,每次函数执行时重新获取,避免GAS全局缓存导致读到旧值。 - 所有绑定到卡片组件的Action回调函数,必须返回
ActionResponse,支持的操作包括更新当前卡片、推送/弹出新卡片、弹出通知等,否则UI状态不会同步。 - 不要在全局作用域执行属性写入操作,全局代码会在每次脚本冷/热启动时执行,很容易覆盖用户已经保存的自定义值。
内容的提问来源于stack exchange,提问作者GN.
相关产品推荐
相关产品推荐

