写在前面:

为公共事业做贡献,做了个开源版本:scratch-cn.lite

开源版本带MySQL后台服务器,功能:注册、登录、保存作品、分享、修改作品名称、保存作品缩略图。

有兴趣的朋友可以去下载参考:https://gitee.com/scratch-cn/lite

Scratch二次开发的纯技术交流QQ群:115224892

今天的内容有点硬核,不太熟悉Scratch源代码的朋友或是对REACT不太熟悉的朋友,可能会略感不适!!!

一、Scratch作品生命状态图

所有的平台、技术都是围绕数据内容做操作的。Scratch也不例外:它的一切过程,最终都会成为一个作品!!!

Scratch二次开发过程中,也会绕不开作品的加载、修改及保存(既:增、删、改、存)。

本人通过Scratch作品的加载方式,分为三类作品,再来看这些作品的不同的生命周期。

先上一张图:

Scratch作品生命周期图

本文就是根据上面Scartch作品的三种不同加载方式,把作品分类,来讨论Scartch作品的加载、保存的。

二、Scratch作品的三种不同加载方式

1、从本机加载;

2、Scratch默认自带;

3、从服务器加载。

三、Scratch作品状态源代码

这三类作品的状态,主要体现在Scratch源代码中的一个:project-state.js(源文件在reducers目录下),其主要内容为(篇幅有限,就不全部贴出来了,只贴重要部分):

const LoadingState = keyMirror({NOT_LOADED: null,ERROR: null,AUTO_UPDATING: null,CREATING_COPY: null,CREATING_NEW: null,FETCHING_NEW_DEFAULT: null,FETCHING_WITH_ID: null,LOADING_VM_FILE_UPLOAD: null,LOADING_VM_NEW_DEFAULT: null,LOADING_VM_WITH_ID: null,MANUAL_UPDATING: null,REMIXING: null,SHOWING_WITH_ID: null,SHOWING_WITHOUT_ID: null,UPDATING_BEFORE_COPY: null,UPDATING_BEFORE_NEW: null
});
const initialState = {error: null,projectData: null,title: "Scratch作品",projectId: null,loadingState: LoadingState.NOT_LOADED
};
const reducer = function (state, action) {if (typeof state === 'undefined') state = initialState;switch (action.type) {case DONE_CREATING_NEW:// We need to set project id since we just created new project on the server.// No need to load, we should have data already in vm.if (state.loadingState === LoadingState.CREATING_NEW) {return Object.assign({}, state, {loadingState: LoadingState.SHOWING_WITH_ID,projectId: action.projectId});}return state;case DONE_FETCHING_WITH_ID:if (state.loadingState === LoadingState.FETCHING_WITH_ID) {return Object.assign({}, state, {loadingState: LoadingState.LOADING_VM_WITH_ID,projectData: action.projectData});}return state;case DONE_FETCHING_DEFAULT:if (state.loadingState === LoadingState.FETCHING_NEW_DEFAULT) {return Object.assign({}, state, {loadingState: LoadingState.LOADING_VM_NEW_DEFAULT,projectData: action.projectData});}return state;case DONE_LOADING_VM_WITHOUT_ID:if (state.loadingState === LoadingState.LOADING_VM_FILE_UPLOAD ||state.loadingState === LoadingState.LOADING_VM_NEW_DEFAULT) {return Object.assign({}, state, {loadingState: LoadingState.SHOWING_WITHOUT_ID,projectId: defaultProjectId});}return state;case DONE_LOADING_VM_WITH_ID:if (state.loadingState === LoadingState.LOADING_VM_WITH_ID) {return Object.assign({}, state, {loadingState: LoadingState.SHOWING_WITH_ID});}return state;case DONE_LOADING_VM_TO_SAVE:if (state.loadingState === LoadingState.LOADING_VM_FILE_UPLOAD) {return Object.assign({}, state, {loadingState: LoadingState.AUTO_UPDATING});}return state;case DONE_REMIXING:// We need to set project id since we just created new project on the server.// No need to load, we should have data already in vm.if (state.loadingState === LoadingState.REMIXING) {return Object.assign({}, state, {loadingState: LoadingState.SHOWING_WITH_ID,projectId: action.projectId});}return state;case DONE_CREATING_COPY:// We need to set project id since we just created new project on the server.// No need to load, we should have data already in vm.if (state.loadingState === LoadingState.CREATING_COPY) {return Object.assign({}, state, {loadingState: LoadingState.SHOWING_WITH_ID,projectId: action.projectId});}return state;case DONE_UPDATING:if (state.loadingState === LoadingState.AUTO_UPDATING ||state.loadingState === LoadingState.MANUAL_UPDATING) {return Object.assign({}, state, {loadingState: LoadingState.SHOWING_WITH_ID});}return state;case DONE_UPDATING_BEFORE_COPY:if (state.loadingState === LoadingState.UPDATING_BEFORE_COPY) {return Object.assign({}, state, {loadingState: LoadingState.CREATING_COPY});}return state;case DONE_UPDATING_BEFORE_NEW:if (state.loadingState === LoadingState.UPDATING_BEFORE_NEW) {return Object.assign({}, state, {loadingState: LoadingState.FETCHING_NEW_DEFAULT,projectId: defaultProjectId});}return state;case RETURN_TO_SHOWING:if (state.projectId === null || state.projectId === defaultProjectId) {return Object.assign({}, state, {loadingState: LoadingState.SHOWING_WITHOUT_ID,projectId: defaultProjectId});}return Object.assign({}, state, {loadingState: LoadingState.SHOWING_WITH_ID});case SET_PROJECT_ID:// if the projectId hasn't actually changed do nothingif (state.projectId === action.projectId) {return state;}// if we were already showing a project, and a different projectId is set, only fetch that project if// projectId has changed. This prevents re-fetching projects unnecessarily.if (state.loadingState === LoadingState.SHOWING_WITH_ID) {// if setting the default project id, specifically fetch that projectif (action.projectId === defaultProjectId || action.projectId === null) {return Object.assign({}, state, {loadingState: LoadingState.FETCHING_NEW_DEFAULT,projectId: defaultProjectId});}return Object.assign({}, state, {loadingState: LoadingState.FETCHING_WITH_ID,projectId: action.projectId});} else if (state.loadingState === LoadingState.SHOWING_WITHOUT_ID) {// if we were showing a project already, don't transition to default project.if (action.projectId !== defaultProjectId && action.projectId !== null) {return Object.assign({}, state, {loadingState: LoadingState.FETCHING_WITH_ID,projectId: action.projectId});}} else { // allow any other states to transition to fetching project// if setting the default project id, specifically fetch that projectif (action.projectId === defaultProjectId || action.projectId === null) {return Object.assign({}, state, {loadingState: LoadingState.FETCHING_NEW_DEFAULT,projectId: defaultProjectId});}return Object.assign({}, state, {loadingState: LoadingState.FETCHING_WITH_ID,projectId: action.projectId});}return state;case START_AUTO_UPDATING:if (state.loadingState === LoadingState.SHOWING_WITH_ID) {return Object.assign({}, state, {loadingState: LoadingState.AUTO_UPDATING});}return state;case START_CREATING_NEW:if (state.loadingState === LoadingState.SHOWING_WITHOUT_ID) {return Object.assign({}, state, {loadingState: LoadingState.CREATING_NEW});}return state;case START_FETCHING_NEW:if ([LoadingState.SHOWING_WITH_ID,LoadingState.SHOWING_WITHOUT_ID].includes(state.loadingState)) {return Object.assign({}, state, {loadingState: LoadingState.FETCHING_NEW_DEFAULT,projectId: defaultProjectId});}return state;case START_LOADING_VM_FILE_UPLOAD:if ([LoadingState.NOT_LOADED,LoadingState.SHOWING_WITH_ID,LoadingState.SHOWING_WITHOUT_ID].includes(state.loadingState)) {return Object.assign({}, state, {loadingState: LoadingState.LOADING_VM_FILE_UPLOAD});}return state;case START_MANUAL_UPDATING://console.log("已点击保存,当着作品状态:"+state.loadingState);if (state.loadingState === LoadingState.SHOWING_WITH_ID||state.loadingState === LoadingState.SHOWING_WITHOUT_ID) {return Object.assign({}, state, {loadingState: LoadingState.MANUAL_UPDATING});}return state;case START_REMIXING:if (state.loadingState === LoadingState.SHOWING_WITH_ID) {return Object.assign({}, state, {loadingState: LoadingState.REMIXING});}return state;case START_UPDATING_BEFORE_CREATING_COPY:if (state.loadingState === LoadingState.SHOWING_WITH_ID) {return Object.assign({}, state, {loadingState: LoadingState.UPDATING_BEFORE_COPY});}return state;case START_UPDATING_BEFORE_CREATING_NEW:if (state.loadingState === LoadingState.SHOWING_WITH_ID) {return Object.assign({}, state, {loadingState: LoadingState.UPDATING_BEFORE_NEW});}return state;case START_ERROR:// fatal errors: there's no correct editor state for us to showif ([LoadingState.FETCHING_NEW_DEFAULT,LoadingState.FETCHING_WITH_ID,LoadingState.LOADING_VM_NEW_DEFAULT,LoadingState.LOADING_VM_WITH_ID].includes(state.loadingState)) {return Object.assign({}, state, {loadingState: LoadingState.ERROR,error: action.error});}// non-fatal errors: can keep showing editor state fineif ([LoadingState.AUTO_UPDATING,LoadingState.CREATING_COPY,LoadingState.MANUAL_UPDATING,LoadingState.REMIXING,LoadingState.UPDATING_BEFORE_COPY,LoadingState.UPDATING_BEFORE_NEW].includes(state.loadingState)) {return Object.assign({}, state, {loadingState: LoadingState.SHOWING_WITH_ID,error: action.error});}// non-fatal error; state to show depends on whether project we're showing// has an id or notif (state.loadingState === LoadingState.CREATING_NEW) {if (state.projectId === defaultProjectId || state.projectId === null) {return Object.assign({}, state, {loadingState: LoadingState.SHOWING_WITHOUT_ID,error: action.error});}return Object.assign({}, state, {loadingState: LoadingState.SHOWING_WITH_ID,error: action.error});}return state;case SET_PROJECT_NEW_ID://设置刚被新建保存到服务器的作品id及作者IDreturn {...state, projectId: action.projectId};case SET_PROJECT_TITLE://修改作品名称return {...state, title: action.title};default:return state;}
};

四、Scratch作品状态讲解

如果看不懂各作品的状态之间的关系,想要自己做点啥都会有点傻眼,您会发现下不了口。

本人特意把各类状态按加载显示阶段的顺序排列,然后再一一加以说明。

Scratch最初的状态:

NOT_LOADED:没有加载任何作品时的状态。此状态为Scratch初次加载时的一个必经状态,此时Scratch没有加载任何作品数据,也不知道要从哪里加载作品。

Scratch加载作品的状态:

FETCHING_NEW_DEFAULT:从Scratch内部加载default-project目录下的那个默认作品。(作品ID=0,我们也可当他没有作品ID)。

FETCHING_WITH_ID:根据给出的作品ID,从服务器加载一个作品。

CREATING_NEW:创建一个新的作品。(注:会判断前一个作品是否需要保存,并加载Scratch的默认作品,即src/lib/default-project目录下的那个作品,也就是我们一打开就会看到的好个小猫猫的空白作品)。

CREATING_COPY:复制当前作品为一个新的作品(没有作品ID)。

REMIXING:改编当前作品,即复制当前作品(新作品同样没有作品ID)。

Scratch作品加载到VM中的状态:

LOADING_VM_FILE_UPLOAD:从本机直接加载一个作品到VM中。(这个最好理解,此时也没有作品ID)。

LOADING_VM_NEW_DEFAULT:把已经fetch成功的默认作品加载到VM中。

LOADING_VM_WITH_ID:把已经fetch成功的服务器作品加载到VM中。

Scratch作品的正常显示状态:

SHOWING_WITH_ID:当前状态表示作品已有ID,即在服务器上有了,可以做一些有作品ID时的动作,比例:自动保存。

SHOWING_WITHOUT_ID:当前状态表示作品是一个默认作品或是从本机加载的作品。

Scratch保存作品的状态:

 AUTO_UPDATING:自动保存当前作品到服务器。(进入这个状态的一个前提:当前作品是从服务器加载的,通俗的讲,就是这个作品是用ID的。顺便聊一句:从本机加载或是新建的作品,都是没有作品ID的,所以无法自动保存,毕竟服务器上的作品,都是需要有作品ID,才知道谁是谁。)

MANUAL_UPDATING:手动保存作品。(即用户主动点击保存按钮时触发)

UPDATING_BEFORE_NEW:新建作品前,保存当前作品。

UPDATING_BEFORE_COPY:复制作品前,保存当前作品。

Scratch作品的其他状态:

ERROR:加载作品过程中出错的状态,用于报警与回到一个正确的状态。

五、综述

根据上面的各种状态的描述及配合最上面的图,基本上可以明白Scratch作品的生命周期。

个人也是看了一天的时间,才把这里面的弯弯绕看明白。

觉得这个Scratch的各种状态非常复杂,也没明白老外为什么这么设计,感觉他们有点傻傻的(玩笑话,当真你就输了:))。

于是自己动手,丰衣足食:把Scratch作品状态从原来的 16 种简化到只有 7 种。

把整个Scratch作品的生命周期给简化了。然后Scratch系统也自然而然的给简化了。

简化后大大减少的状态的种类,并且功能更加多了。

在此简单的贴一下本人简化后的状态源代码(仅供参考):

简化后的状态图
// 作品的各种状态
const LoadingState = keyMirror({ERROR: null,NOT_LOADED: null,FETCHING_WITH_ID: null,LOADING_VM_WITH_ID: null,SHOWING_WITH_ID: null,//显示的正常作品:用户自己的作品或是别人的作品MANUAL_UPDATING: null,//开始手动更新作品LOADING_VM_FILE_UPLOAD: null,//从本机加载作品时的状态
});
const defaultProjectId = 0;
const initialState = {loadingState: LoadingState.NOT_LOADED,//当前作品状态error: null,//设置projectChanged==true的条件:// 1、作品源代码被更改// 2、新建、复制、改编、上传的作品(同时,设置作品ID=0、作品作者ID=0)projectChanged:false,//作品是否已改变,即为是否需要保存//作品部分=====================================================projectData: {},//整个作品的JSON格式的源代码projectId: 0,//作品IDtitle:'',//作品名称authorId: 0,//作者IDstate:0,// 0:未发布;// 1:已发布;// 2:已开源;(开源的必须发布)//作业部分=====================================================homeworkProjectId:0,//>0时,作品为一个课程的作业student_id:0,//>0时,表示作品可以设置为作业,且其值就是学生报班是student表中的IDclass_id:0,//班级Id
};
const reducer = function (state, action) {if (typeof state === 'undefined') state = initialState;switch (action.type) {case SET_PROJECT_ID://设置准备加载的作品IDif (state.loadingState === LoadingState.NOT_LOADED || state.loadingState === LoadingState.SHOWING_WITH_ID) {// 作品ID没变:直接返回原状态,阻止重新加载projectif (state.projectId>0 && state.projectId === action.projectId) {return state;}// 作品ID变了:设置状态为FETCHING_WITH_ID,即通知对应组件请求新的project数据        return Object.assign({}, state, {loadingState: LoadingState.FETCHING_WITH_ID,projectId: action.projectId});}case DONE_FETCHING_WITH_ID://对已经下载好了作品,做数据配置if (state.loadingState === LoadingState.FETCHING_WITH_ID) {// 已经获取到作品数据,开始加载到VM中:LOADING_VM_WITH_IDif (action.projectData.student_id){state.student_id = action.projectData.student_id;if (action.projectData.homeworkProjectId){state.homeworkProjectId = action.projectData.homeworkProjectId;}if (action.projectData.class_id){state.class_id = action.projectData.class_id;}}if (action.projectData.lesson_id){state.lesson_id = action.projectData.lesson_id;}if (action.projectData.card_count){state.card_count = action.projectData.card_count;}return Object.assign({}, state, {loadingState: LoadingState.LOADING_VM_WITH_ID,projectChanged: action.projectData.id==defaultProjectId,//自动设置改变标签//作品ID:如果是默认作品,则Id也设置为0,与复制、改编、上传的作品被保存前的Id统一为0projectId: action.projectData.id==defaultProjectId?0:action.projectData.id,projectData: action.projectData.src,//作品JSON源代码authorId: action.projectData.authorid,//作者IDstate:action.projectData.state,//分享:是否分享title:action.projectData.title,//作品名称//作业相关部分class_id:state.class_id,homeworkProjectId:state.homeworkProjectId,student_id:state.student_id});}case DONE_LOADING_VM_WITH_ID://已经加载到VM,设置作品状态为SHOWING_WITH_IDif (state.loadingState === LoadingState.LOADING_VM_WITH_ID) {return {...state, loadingState: LoadingState.SHOWING_WITH_ID};}case COPY_PROJECT://复制作品:修改作品ID=0、作品作者ID=0if (state.loadingState === LoadingState.SHOWING_WITH_ID) {return Object.assign({}, state, {projectChanged: true,//自动设置改变标签projectId: 0,//作品IDauthorId: 0,//作者IDstate:0,//分享:是否分享});}case START_UPDATING://开始更新if (state.loadingState === LoadingState.SHOWING_WITH_ID) {return {...state, loadingState: LoadingState.MANUAL_UPDATING};}case DONE_UPDATING://上传更新成功if (state.loadingState === LoadingState.MANUAL_UPDATING) {return {...state, loadingState: LoadingState.SHOWING_WITH_ID};}case SET_PROJECT_NEW_ID://设置刚被新建保存到服务器的作品id及作者IDreturn {...state, authorId:action.authorId, projectId: action.projectId};case START_LOADING_VM_FILE_UPLOAD://加载本地作品if (state.loadingState === LoadingState.SHOWING_WITH_ID) {return {...state, loadingState: LoadingState.LOADING_VM_FILE_UPLOAD};}case DONE_LOADING_VM_FILE_UPLOAD://加载本地作品完毕if (state.loadingState === LoadingState.LOADING_VM_FILE_UPLOAD) {return Object.assign({}, state, {loadingState: LoadingState.SHOWING_WITH_ID,projectChanged: true,//自动设置改变标签projectId: 0,//作品IDauthorId: 0,//作者IDstate:0,//分享:是否分享title: action.title,//作品名称error: null,projectData: {}//整个作品的JSON格式的源代码                      });}case RETURN_TO_SHOWING:return {...state, loadingState: LoadingState.SHOWING_WITH_ID};case SET_PROJECT_TITLE://修改作品名称return {...state, title: action.title};case SET_PROJECT_SHARE://修改作品分享状态return {...state, state:1};case SET_PROJECT_CHANGED://设置作品是否需要被保存//console.warn("BLJ:设置作品是否需要被保存"+action.changed);return {...state, projectChanged: action.changed};case SET_HOMEWORK_PROJECT://设置作品为作业state.homeworkProjectId = action.workId;//用户在新建、打开其他作品时,作业ID应该一直有效return {...state, homeworkProjectId: action.workId};case START_ERROR:// fatal errors: there's no correct editor state for us to showif ([LoadingState.FETCHING_WITH_ID,LoadingState.LOADING_VM_WITH_ID].includes(state.loadingState)) {return initialState}// non-fatal errors: can keep showing editor state fineif ([LoadingState.MANUAL_UPDATING].includes(state.loadingState)) {return Object.assign({}, state, {loadingState: LoadingState.SHOWING_WITH_ID,error: action.error});}// default: return state;}return state;
};

写在后面:

如果本文章对您有帮助,请不吝点个赞再走(点赞不要钱,只管拼命赞)!!!

您的支持,就是本人继续分享的源动力,后续内容更加硬核+精彩,请 收藏+关注 ,方便您及时看到更新的内容!!!

Bailee 了个Bye!!!

Scratch二次开发7:Scratch3.0作品的生命周期(各类状态)分析讲解相关推荐

  1. .NET Core 3.0 即将结束生命周期,建议迁移 3.1

    .NET Core 官方发布博客,说明 .NET Core 3.0 即将结束生命周期,建议开发者迁移到 3.1 版本. .NET Core 3.0 于 2019 年 12 月 3 日发布,这是一个 C ...

  2. atitit.提升开发效率---使用server控件生命周期 asp.net 11个阶段 java jsf 的6个阶段比較...

    atitit.提升开发效率---使用server控件生命周期  asp.net 11个阶段  java jsf 的6个阶段比較 例如以下列举了server控件生命周期所要经历的11个阶段. (1)初始 ...

  3. (二)Java线程与系统线程,生命周期

    本专栏多线程目录: (一)线程是什么 (二)Java线程与系统线程和生命周期 (三)Java线程创建方式 (四)为什么要使用线程池 (五)四种线程池底层详解 (六)ThreadPoolExecutor ...

  4. 【Android】8.0活动的生命周期(一)——理论知识、活动的启动方式

    1.0 Android是使用任务(Task)来管理活动的,活动就像栈一样堆放着在一起. 每个活动的生命周期最多可能会有四种状态: 1.1 运行状态 位于栈顶 1.2 暂停状态 不在栈顶但在界面上仍处于 ...

  5. Scratch二次开发0:少儿编程平台功能设计及各框架应用

    自打自己进入少儿编程这行,对这个行业慢慢的有所了解.以前基本上什么编程语言都用过,反正是需要开发的应用是合适用什么编程语言,就去使用,还好,对于编程选择的语言工具,对本人没障碍. 下面是自己的所思所想 ...

  6. scratch二次开发(一)

    一.scratch模块 ## scratch-vm 虚拟机解析加载序列化项目文件.扩展功能实现.根据相应事件渲染舞台### scratch-audio 声音引擎解析.播放声音### scratch-b ...

  7. 【五一创作】使用Scala二次开发Spark3.3.0实现对MySQL的upsert操作

    使用Scala二次开发Spark实现对MySQL的upsert操作 背景 在我们的数仓升级项目中,遇到了这样的场景:古人开发的任务是使用DataStage运算后,按照主键[或者多个字段拼接的唯一键]来 ...

  8. wap六感程序二次开发_Cscms v4.0 二次开发y2002音乐网站程序

    Cscms v4.0 二次开发y2002音乐网站程序 源码简介: 修复了多处问题,比网上流传的版本要完整很多. 程序包括pc+wap,页面功能和原y2002基本一样. 程序比较完整了,但还是会有bug ...

  9. E:大疆M300二次开发PSDKV2.1.0。无法识别无人机型号。一直出现 aircraft type 0

    连接好设备后(M300无人机,开发套件2.0,选用树莓派4B),可以运行示例程序,但是一直打印 [35.664][module_core]-[Info]-[PsdkCore_Init:134]PSDK ...

  10. Revit二次开发实现BIM盈利(以橄榄山快模为例讲解) 视频讲座下载

    应笔墨闲谈群的邀请, 在10月11号晚8:30分在其群做了一次关于BIM二次开发的讲座. 由于参与者基本上都是从设计院和施工单位来的,所以对Revit二次开发做了纵览性的讲解, 以非程序员能听懂的方式 ...

最新文章

  1. excel去掉超链接
  2. UNIX下C语言的图形编程-curses.h函数库
  3. 20190408 Java中的Double类型计算
  4. 人工玻璃体-技术指标汇总(持续更新中)
  5. 线性链表java实现_java实现线性单链表
  6. linux webshell 页面管理,instantbox:30s内快速搭建可通过webshell管理的Linux系统
  7. C# 自定义配置文件
  8. 当网络安全遇上大数据分析(1)
  9. python封装成exe后运行失败_python 在编译成EXE 文件后报错 我实在是找不出原因
  10. FastDFS单机搭建以及java客户端Demo
  11. wordpress建立二级导航菜单
  12. MySql表空间的概念
  13. Python科学计算函数库介绍
  14. BIM族库下载——BIM项目停车场管理系统常用族库
  15. Ceres 曲线拟合
  16. AltiumDesigner2020导入3D Body-SOLIDWORKS三维模型
  17. 智能计算系统课程笔记05:编程框架机理
  18. ROS 简单的跨浏览器通信
  19. java生成word样式变形的解决方案(freemarker生成word的样式不对)
  20. 谈一谈a:link、a:visited、a:hover、a:active的正确使用顺序

热门文章

  1. 撰写商业计划书的一些误区和建议
  2. Linux下使用dos2unix修改目录中文件格式
  3. 求你们不要再问我录屏软件了,这些电脑、手机录屏软件全给你们!
  4. 使用Javassist 动态生成类
  5. Knockout.js入门级
  6. kpw4换壁纸_发挥全部潜能,Kindle越狱指南
  7. pe擦除服务器硬盘,怎么彻底删除硬盘数据
  8. 计算机网络实验——ns3仿真最短路由选择算法
  9. 使用 GO-CQHttp或mirai框架 搭建QQ的机器人
  10. 虚拟化的软件有哪些?区别是什么?