工作日志
9.1
学习内容:
- 连接公司内网+观看公司宣传片
- 完成入职培训+签署电子合同
- 熟悉公司环境和团队成员
- 熟悉ZMP+邮箱
- 注册语雀账号并加入团队
- 搭建前端开发环境(下载git+svn)
- 了解公司前端开发规范+技术栈等
- 阅读团队日常文档
9.2
学习内容:
- 由于之前基本只接触过vue,现在开始学习react,今天完成react官网实践教程demo的实现及原理理解;
- 观看并学习22级数渠班的汇报内容
学习困难:
react跟vue比较起来还是有比较多的不同:
react是通过JSX渲染模板。而vue是通过一种拓展的HTML语法进行渲染:vue看起来更像是已经给你搭建好了房子,你所做的只需要把它装饰得更美丽;而react更像搭积木,偏向于将功能一个一个组件化独立出来。
除了框架本质的不一样,两者之间还有许多不一样的地方,比如:
渲染过程–在vue中可以只单独渲染发生变化的组件,但是react需要将所有组件重新渲染;
数据流–在vue中通过v-model可以实现数据的双向绑定,而react中仅有单向数据流;
数据监听–vue中通过比较算法(diff)比较VDOM,监听变化的dom,但是如果没有设立设置key进行过程优化,可能会有大量VDOM重新渲染,然而react就不会发生此类情况,因为react强调数据的不可变。
当然除了不同以外,vue和react还有许多相似的地方:
都支持组件化开发、都有VDOM、都支持props进行父子组件通信等。
以上仅个人理解,不一定准确,以后深入了解会发现更多的一样和不一样~
学习心得与感悟:
今天观看的汇报内容是22级数渠的师姐师兄的周会,他们负责的项目是数渠中心组件库的官方教学网站,类似于elementUI。总局在汇报中对项目的细节把控令我十分敬佩,我能感受到总局对于用户体验以及员工的项目能力是十分重视的,大到这个界面不知所云,要重新确定需求和设计稿;小到这里少个空格,那里大小写不一致都会被一一指出。这样的组会是很有意义的,可以很好地确定项目开发进度、把握方向不要出错,前端开发特别要抠细节,时刻站在用户的角度看待问题。我感觉以用户体验为中心该是浩鲸一贯的作风,只有心心念念为用户,用户才会信任我们,才能长久和睦。
9.5
学习内容:
今天系统深入学习了React初级知识体系:
- React与JSX关系
- 元素渲染&组件&Props
- State&生命周期
- 事件处理
- 条件渲染
- 列表&Key
- 状态提升
- 组合VS继承
学习困难:
React&Vue细节比较学习①:(最大的不同是语法模板的编写)
React
- 在**{}**中使用表达式or变量
- 主要使用JS语法进行条件渲染
- 数据流单向且自上而下传递:通过状态提升(即将需要共享的state移动到最近的共同父组件中)达到共享数据源的目的
- 推广了VDOM并创造了新的语法–JSX,允许开发者在js中书写HTML
- props对于子组件来说是必须的,因为它依赖一个单一数据源作为它的状态;使用map()进行循环赋值/创建节点
- 父子组件数据通信主要采用props;跨层级组件传递数据主要通过context。特别是在子组件向父组件传递数据时一般采用回调函数
- 创建应用使用create-react-app脚手架
- 需要使用setState方法去更新状态
Vue
- 在中使用表达式or变量
- 主要使用模板语法v-if/v-show进行条件渲染
- 数据双向绑定:通过v-model语法糖实现数据的双向绑定
- 使用的模版是普通的HTML,通过Vue来整合现有的系统是比较容易的,不需要整体重构
- 与props略有不同,他们一样是在组件中被定义,但Vue依赖于模版语法,可以通过v-for循环更高效地展示传入的数据等
- 父子组件传值主要使用props/$emit通信、获取属性和方法主要使用$refs/ref+$children/$parent;兄弟组件数据通信主要使用新建Vue实例通过$emit/$on+eventBus通信;跨层级组件间的通信主要使用provide/inject通信。特别是在子组件向父组件传递数据时一般采用事件
- 创建应用使用vue-cli脚手架
- Vue中state对象并不是必须的,数据由data属性在Vue对象中进行管理,通过计算属性/深度监视中的getter&setter方法更新data属性
学习心得与感悟:
仔细学习下来,React和Vue中有许多细节的不同之处,比如变量的使用也许仅仅只是一个{}的区别,又比如整体框架思想、模板都不一样。Vue是MVVM模式的框架,整合了angular+react,上手更加简单。但正因为它的入门简单,造成了它的实质远不止表面上看起来那么简单,深究源码会发现它的许多神奇之处;而react的严谨+拥有稳定的运营维护也正是大家它成为大型项目必备框架的原因。除了不一样,也有许多相同理念,比如他们在VDOM的比较算法上的实质其实基本一致,都采用diff算法比较节点,这也是为什么不支持index作为key的主要原因:一是效率大幅度降低,二是可能会造成数据错误。目前对于react的学习还未进入中高级阶段,在未来更深入地学习之后,相信我也会逐渐摸清react的所有细节。
9.6
学习内容:
今天系统深入学习React高级指引+阅读实战项目:
- Fragments
- Import
- Context
- Refs
学习困难:
Fragments:React中,Fragments的用法类似于Vue中的Slot
- 在Android原生开发中,fragments也是一个非常重要的概念,比如想要应用在手机与iPad上展示不同的排版效果,可以通过设置fragments来实现,包括在写应用的子界面的时候(底部有个bar,点击不同按钮跳转不同界面,但不会重新刷新整个界面,其实也就是类似于vue中的submenu的路由跳转)
- vue的插槽需要在组件中定义slot标签,并且在引用的时候要对应slot标签上的插槽名;
- react的插槽直接将dom写到组件的标签下然后子组件通过this.props.children获取到放入的dom元素,这个dom元素可以设置key属性来区分每一个元素。然后在组件中直接将dom当成组件引入到render中。
Import:React与Vue中Import的含义类似,用法有略微不同
- vue中的import只能用于导入组件/工具之类的文件
- react的import不仅可以导入组件和工具文件,还可以导入某个组件的某个功能函数,如下:add()为math.js中的一个功能函数
Context :React和Vue中都引入了provide的概念
- react中,context用于简化值层层传递所有组件的繁琐,常用API有React.createContext/Context.Provider/Classs.contextType/Context.Consumer/Context.displayName
- vue中,数据之间的层层传递通常使用provide+inject进行
父组件
子组件
Refs:React中与Vue中均有Refs转发的概念,用法也基本一致
refs主要用于将ref自动地通过组件传递到其一子组件的技巧。对于大多数应用中的组件来说,这通常不是必需的。但其对某些组件,尤其是可重用的组件库是很有用的。
注意:
- ref 加在普通的元素上,用this.$refs.(ref值) 获取到的是dom元素
- ref 加在子组件上,用this.$refs.(ref值) 获取到的是组件实例,可以使用组件的所有方法。在使用方法的时候直接this.$refs.(ref值).方法()就可以使用了
学习心得与感悟:
今天看完react这些特性之后,我发现除了模板语法外,vue和react真的是很像,基本组件库、工具的基本含义都几乎一样,唯一不同的可能就是工具的作用范围和工作用法了。比如我就喜欢在使用Echarts的时候,将需要展示的数据图放在一个div里,div我就会使用ref命名,然后调用Echarts的时候就直接this.$refs(‘divName’)…我感觉接下来的学习也变得轻松一点了,师傅给了我一个公司的H5项目研究学习,因为我个人比较喜欢在实战中学习新技术,上手极快而且学的也扎实。今天只简单阅读了项目的结构,将其大致分了功能。明天开始就着重阅读项目语法!
9.7
学习内容:
Umi框架学习笔记①
ps.本来想着,把实战项目的结构搞明白了,可以开始内容研究了。结果在刚开始研究router的时候发现公司的项目的router并不是react经典配置,也不是ReaxtRouter6配置,而且多了一个exact参数,再一问师傅得知是用umi写的,之前也没学过这个,那就只能快速开始umi的边看边学了。
学习困难:
umi入门介绍:umi是阿里研发的一个可插拔的react应用框架,是一个方便react开发的前端框架,配置路由、构建、打包部署什么的省了不少事。不过如果你想自己DIY用wbepack打包、配置其他的路由方式什么的就不太适合,因为他相当于为了方便给你统一了流程,就像寄快递一样,可以自己打包好去寄,也可以让菜鸟驿站给你打包,那样可能就根据他们统一的流程,不能自己DIY。
umi创建项目:官方推荐是使用yarn,不过你要用npm、cnpm应该也是可以的,然后使用脚手架@umijs/create-umi-app创建项目就行,下载依赖npm i / yarn,启动就npm run start/yarn start。如果需要使用其他的组件库,还需要修改配置:编辑 umirc.ts配置 layout{}+安装组件
umi项目结构:快速上手阅读项目必备组织结构指南!学完umi就麻溜看项目
umi配置:在umi给的官方文档中,相比于JavaScript,umi其实更支持TypeScript。虽然说JS也已经升级到ES6了,但是TS相对来说会比JS更实用。TS实际上是JS的一个超集,同样支持ES6,可以编译为纯JS,可以运行在任何浏览器上,更适用于开发大型应用。
umi配置路由:在umi中配置路由是通过routes进行的,形如
这个形式已经非常接近vue中的路由配置了
- 子路由:配置子路由时,umi中使用routes中嵌套routes,而vue中使用routes中嵌套children;
- 重定向:通过redirect:’path’进行重定向,umi和vue一致;
- 路由跳转:
如果是声明式,umi导入link并在界面使用;vue则使用
命令式:umi中的history/hash模式为先导入,然后直接调用
命令式:vue中的history/hash只需要在router.js中声明mode:’history’/‘hash’直接配置,跳转路由时使用this.$router.push(‘path’)
- 约定式路由:约定式路由也叫文件路由,就是不需要手写配置,文件系统即路由,通过目录和文件及其命名分析出路由配置。类似Java中的文件结构,比如必须有个项目文件夹,一个源文件夹,一个包,包下面才能创建Java文件。只要满足这种目录结构,会自动生成包名路径。
- Mock文件:umi中mock的用法和vue中基本一致,形如:
学习心得与感悟:
今天初步学习umi初级知识,感觉在umi里开发react对我来说可能更简单一点,因为有了umi,react和vue的项目开发差别变小了,甚至是更快捷了。原先的react有可能这样
9.8
学习内容:
快速上手React项目阅读
umi基本学习结束,现在开始阅读clp-shopping项目
学习困难:
- umi项目的目录结构
项目文件夹
- constants:常量文件,类似于安卓开发里的title、id的值系统会建议你统一存储在一个xml文件中
看了README,试图运行项目时遇到困难:报502错误。问了师傅之后,发现应该是测试环境问题,改了3、4个地址之后成功运行。
虽然说是运行了吧,但是由于接口+没人维护的原因,产品基本只能通过路由跳转,图片刷新也有问题,有些页面也不能正常跳转。
浏览器测试页面功能:
缺点:
刚进页面提示需要登录,但是并未找到登录按钮,只能通过/login/SignIn进入登录界面,并且需要验证码,但无发送验证码按钮。注册时仅提供邮箱注册,但登录时却要求手机号+未知验证码。
重新回到主界面,点击商品选择框无效果,点击个别商品可以进入商品详情页,加入购物车按钮以及立即购买按钮均可以点击,但无法继续点击下一步确定。
- 查看组件代码
原本想看主界面代码打算看MainLayout,发现没什么内容,代码简洁的给我的感觉像java里的interface,就那么几行,看起来没什么内容,通过路由查看,发现想阅读主界面代码看home就行。
- home:
只引入了一个iconfont组件,其他的基本都是开发所需库、umi路由等
ES6新语法,const content = props.content的简写
useEffect是一个hook,主要用来数据请求、事件处理、订阅等。在组件mount时执行,但也会在组件更新时执行。在useEffect中,不仅会请求后端的数据,还会通过调用setData来更新本地的状态,这样会触发view的更新。因为我们在每次请求数据之后都会设置本地的状态,所以组件会更新,因此useEffect会再次执行,因此出现了无限循环的情况。我们只想在组件mount时请求数据。我们可以传递一个空数组作为useEffect的第二个参数,这样就能避免在组件更新执行useEffect,只会在组件mount时执行。useEffect类似vue的生命周期函数beforeMount(),也可以在里面写请求等。
这个home页面中在mount时将需要渲染展示的数据请求完成,获取用户token判断用户购物车内容,等用户加载完成时就会出现应该展示给当前用户看的内容。
JSBridge—移动端开发的相关技术,也就是连接 JS 和 Native 的桥梁,本项目为H5项目,它也是 Hybrid App 里面的核心。一般分为 JS 调用 Native 和 Native 主动调用 JS 两种形式。此处判断特殊URL,一般用于在 Web 端唤醒 App,猜测应该用于广告,比如当用户点击到广告时会提示是否打开某某APP,如果是应用正常链接就直接路由跳转。
其他代码为一些业务逻辑函数以及网页框架内容,阅读难度不大。
学习心得与感悟:
初步阅读实战项目文件,虽然与学习的react教程有些许差异,但主要形式都还是没变,使用起来更为灵活。Hook的话是react16.8新特性,使用起来挺方便的,众所周知,之前的react当你想要使用state时,必须将react实例写成class形式,把state放在构造器里,属实不太方便,有了hook之后方便多了。还有JSBridge,主要用于前端与native交互:Native 与 JS 的通信,会使用 H5 结构化克隆算法来序列化传递的数据,也就是说传递的数据最终会被转换成字符串,所以不能被 JSON.stringify 或其他序列化方法转换的的数据结构就会丢失。
9.9
学习内容
- 参与“910计划”官网验收会议
- hooks学习笔记①
- useState()
- useEffect()
鉴于昨天对hooks理解有些偏差,想要深入了解hooks后再阅读项目代码
学习困难
一、 会议问题
- 会议记录
发现问题
输入框输入文本后颜色依旧为灰色(不知道是不是专门设计的,但是看着不太舒服)
二、学习笔记
学习hooks的原因:在没有hooks之前,如果需要使用state时,要将react实例写成class形式, 然而class 是学习 React 的一个难点。你必须去理解 JavaScript 中 this 还不能忘记绑定事件处理器,这些代码非常冗余。大家可以很好地理解 props,state 和自顶向下的数据流,但对 class 却了解的不那么透彻,也就是昨天小马哥说的,hooks可以使react多使用函数式编程。
useState():useState 就是一个 Hook 。通过在函数组件里调用它来给组件添加一些内部 state。React 会在重复渲染时保留这个 state,它类似 class 组件的 this.setState ,它的返回值为:新的状态和一个让你更新它的函数(比如下面例子中的setCount() ),但是它不会把新旧 state 进行合并。使用时可以用ES6中数组赋值的语法(const [a,b] = [‘a’,’b’];)。在官网中,useState的例子看像vue中的计算属性,但却不完全一样。计算属性 computed() 使用时旨在更改 data 中的属性,当获取到属性值改变时调用setter()进行属性值的更改。
useEffect():useEffect并不等于componentDidMount生命周期,虽然可以使用 useEffect(fn, []) ,但它们并不完全相等。和componentDidMount不一样(目前个人理解是它等于react的生命周期函数,也就是包括但不限于componentDidMount)。useEffect会捕获 props和state。其中第二个参数 [] 如果为空,说明Effect没有使用react数据流里的值,只执行一次;但如果不写第二个参数,可能会造成多次重复请求的后果。如果你的useEffect拿到的是旧值,可以好好查看一下是不是自己用法出现了错误,也可以通过设置ref的方式解决。
学习心得与感悟
① 会议感想
表述问题:描述一个产品时,尽量不要只给客户介绍这个界面有什么内容,大家都能看得见,不需要太多赘述,比如(进入这个界面,这里有xxx,下面有xxx,接下来进入xxx)。更主要的是把重心放在各个界面/功能的逻辑衔接上或者从整体到局部,带客户从头到尾走一遍流程,解释清楚我们现在进入的什么界面,要怎么使用功能,这一步我们是在做什么,要怎么进行下一步,这么进行的原因等。不然客户根本记不住你的产品流程,你在干什么,这一步是在干嘛,怎么突然到这一步了,观感非常差看得想睡觉。比如我之前的比赛项目给评委导师介绍项目时采用的文案如下:
虽然也不完美,但是是有条理的流程演示。
名词问题:介绍产品时永远不要用太多你以为你大家都理解的专业名词或者有冲突歧义的名词,许多客户根本不清楚这些名词的意思,比如(原子、分子),又比如(组件->页面)
展示问题:当你的产品功能还没有全部实现时,不要一上来就给客户说现在有多少多少问题,还没开始介绍印象分就快负数了…尽量演示可以正常使用的功能,在演示功能的时候顺带提一下哪些功能可能有还没有完善的问题。如果不是很紧要的、可以不用演示的小问题就不要说了,自己知道就行回去自己完善好。
交付问题:负责交付的人一定要对自己的产品足够熟悉,包括哪里是优点哪里是缺点,尽力放大优点而缩小缺点。在别人看来浩鲸就是浩鲸,是一个整体,也许内部分配工作时会分为几组,但是客户并不会知道,所以一定要每个小组之间内容共享,告诉交付时可能需要的细节问题,这样在被客户问到时不会手足无措。
回应问题:当客户指出你的问题,千万不要说由于什么原因这里还未开发好,而是尽可能弥补。通过说话的艺术:比如此次项目验收时,在用户没有登录的情况下依旧可以浏览所有网页内容。不要和客户说这里因为设置有不同权限的用户,但还没有完善此功能;可以说:我们有宽广的胸襟,对于页面源码都是可以开放给大家看的,但是一些敏感操作需要用户登录(转发、复制等),这样不会让客户对我们印象太差。
严谨问题:无论是哪个岗位都需要严谨对待,可能是一个小小的卡片内容展示问题,或者是重用组件的对应名字一致问题,又或者是一个箭头的朝向问题。开发时,需求要求哪样,开发时就应该做到哪样,而不是偷工减料或者自作主张变更;测试时一定要认真对照设计文稿与开发所给的界面是不是一模一样,功能有无缺失,大小写是否一致等等细节问题。
② 学习感想
昨天发布的日志中对于hooks中useEffect的用法理解出现偏差,建凯哥给我发了一个链接,虽然我现在还没有看完所有内容,但我对useEffect的理解深入了一些,他并不等于componentDidMount(),更像是等于react的生命周期函数(,也就是说不类似于我认知中的vue的Mount(),而是vue中的生命周期函数。
- React生命周期函数
- Vue的生命周期函数
9.13
学习内容:
商城项目实战①
项目名称:NiceDay
技术栈:React + ReactHook + ReactRouter + Redux + Axios +other
计划完成功能:
- 首页展示
- 城市管理
- 搜索功能
- 上拉加载
- 详情页
- 收藏功能
- 订单评价
计划完成进度:1/7 days
学习困难:
- 环境构建:
- 项目环境:create-react-app 脚手架构建项目
- npx create-react-app NiceDay
- 支持less语法:
- 执行命令:npm run eject(注意使用此命令src不能有任何修改)
- 安装依赖:cnpm install –save-dev less less-loader
- 修改webpack.config.js文件:
- 集成网络请求Axios
- 安装依赖:cnpm i –save axios
- 配置相关文件:
- axios文件request.js:二次封装
- 接口文件api.js:请求方法
- 设置rem布局
在index.html文件中添加动态rem计算方法
- 配置初始样式
引入iconfont文件:这里我使用的是阿里矢量图标库,然后在页面导入
- 路由配置
- 创建页面:Home/Life/Shop/User
- 创建路由:
- 安装依赖:cnpm i –save react-router-dom
- 配置路由:
- 创建底部导航
- 界面
- 框架
- 样式
- 创建顶部导航栏
- 界面
- 框架
- 样式
- 今日成果展示
学习心得与感悟:
由于教程并非最新教程的原因,在观看实战教程时搭建项目也遇到些许问题,很多出现在版本问题。比如最新版react已经不支持querystring、Switch、component等,已经改为最新版本的URLSearchParams/Routes/element…
① less配置:开始配置less后却一直不生效,查找了一番归结于版本问题,于是降版本安装less-loader5.0.0版本,成功生效。
② 路由配置:
配置时出现这些报错原因均为’Switch’ 和’Redirect’ 是 react-router 5版本的接口,而最新版本是”react-router-dom”: “^6.2.1”,并且已经将Switch改为Routes。
由于component已经被替换成element,在使用时不能:
<Route exact path=”/“ element={ Home }>
而是:
<Route exact path=”/“ element={
终于不再报错,成功运行。
③导航样式:
NavLink的默认颜色为蓝色而且带有下划线,消除下划线需要text-decoration:none,更改颜色需要给NavLink定义加一个className,然后再写样式color:gray;
今天的任务并不算很多,但是既要跟上配置思路,又要自己重新敲一遍代码,还有很多很多的版本问题,纠错基本上就花了至少1-2h,很多时候是这个问题解决又引发下一个问题,不过这也很好地锻炼了我的解决问题能力,之前自己写项目的时候不是特别规范,很多细节都是随便处理,解决了就行了,完全不管他代码的整洁程度以及有没有将代码分类。我相信在跟练完这个实战项目后我也可以独立开发出一个质量较高的项目!
9.14
学习内容:
按照总局建议,变更一下项目需求:参照设计图仿写CLP-SHOPPING项目。
商城项目实战②
项目名称:NiceDay
技术栈:React + ReactHook + ReactRouter + Redux + Axios +other
计划完成功能:
- 首页展示
- 充值中心
- 选择套餐
- 购物中心
- 商品详情
- 个人中心
- 个人详情
- 登录注册
计划已进行:2 days
由于个人能力+精力+条件有限,仅能实现部分界面。
今日参考界面:
学习困难:
- 修改底部导航
- 代码规范问题
今天师傅给我发了一份公司的ES6规范,本来是下载FishX插件当做Eslint用,但是不知道为啥在我电脑上没作用,所以还是用的Prettier插件,还可以一键格式刷,方便又快捷。
图标问题
之前用的写的图标,但是师傅给的这个设计稿只能将图标图片下载到本地,奈何 i 标签没有src属性,于是改用:
- 写了之后图片刷不出来,以为是地址问题/名称问题纠结了十几分钟问师傅,原来是react导入图片地址要先import图片路径给变量,然后导入变量才可以:
图标问题就此解决
- 框架问题
在昨天写的界面中我的框架是这样的,每一个都单独写出来,代码十分冗余。
师傅说但是为了代码美观简洁,开发中会将li的数据写成数组,进行遍历,要求尽量将数据格式mock成接口返回的格式,方便后续接口调试,减少工作量。在此之前我只写过vue的多级菜单有采用到v-for循环遍历填充,今天学习了在react中采用map遍历数组的方式进行高效开发:
然而我还是太天真了,想要将图标名和图标地址一起传入li中,于是创造出了这样一个四不像的”数组”,报错后又去求救师傅,师傅说我的对象数组写的不像mock格式,让我将图表名和地址都放在对象里,给他们分别加上键名。于是我顺利完成:
- 定位问题
通过上面操作,我们已经可以得到如下导航栏:
显然跟设计稿还有些差别,这个问题我第一想法是:绝对定位。但我很快认识到这样子是行不通的,我既然已经遍历数组生成了li,那我应该只需要把他们做底部对齐就行,所以很快就想到了flex+align-items的用法,成功解决问题~
- 修改顶部导航
有了底部导航栏的实践,顶部导航栏对我来说容易多啦,需要注意的是对齐问题以及间距问题
- 框架
- 样式
- 界面
- 今日完成界面
- 今日遗留问题:
可以看到底部导航栏的字体颜色,按道理说应该在进入界面时默认情况下home字体与图标为标蓝状态,而后哪个按键被点击哪个就会标蓝,但是我采用的是NavLink编写的路由跳转,所以即使给它样式覆盖也只能做到开始时统一为黑色,当点击时才会标蓝。师傅说我可以用a标签写,自定义点击事件跳转与状态改变,这样子可以解决问题。师傅的思路我理解了,但还没来得及实现,我的想法是目前先把静态页面做完,等到页面大致做完后再加入js事件,完善细节问题。
学习心得与感悟:
从今天早上开始看到公司开发规范的时候就知道我要学习的东西还有很多,我不能为了实现界面和功能而不管代码的清晰度,简洁度以及美观度,而是要在完成的基础上一定要做到代码规范问题。在我之前的项目开发中,代码有点乱七八糟(应该不止一点),对于团队协作以及后期维护来说简直是灾难,经过这次仿写项目,我要做到代码规范+实现react开发项目,了解到更多react的相关开发知识。明天的开发应该会比今天的进展要顺利一些,今天在编写代码时,有很多问题都思考欠缺,比如导航栏定位问题,想当然的先用绝对定位去做了,结果师傅不仅纠正了我的代码简洁规范,还纠正了我开发时的错误思想,是我的代码救命之星。最后感谢小马哥给我安排上了大显示屏,我的神!
9.15
学习内容:
商城项目实战③
项目名称:NiceDay
技术栈:React + ReactHook + ReactRouter + Redux + Axios +other
计划完成功能:
- 首页展示
- 充值中心
- 选择套餐
- 购物中心
- 商品详情
- 个人中心
- 个人详情
- 登录注册
计划已进行:3 days
今日参考设计稿:(已划分盒模型)
学习困难:
- 创建顶部主要功能栏
- 宽高问题:
写这个功能栏的时候,采用的ul+li结构,跟昨天一样用的数组遍历生成li,但是我犯了一个小错误,我将ul的宽度设置成了100%,导致如下问题:
可以看到横向和纵向都出现了滚动条,无法看到完整界面,我询问师傅后,师傅提示我滚动条出现的原因是因为里面的元素宽度或者高度大于外面的元素,然后让我去思考是哪个元素的高或者宽有问题。我今天有问过师傅,是不是在开发H5项目的时候所有的px都要写成rem,师傅给予肯定回答,所以当我在查找错误元素时,发现我有设置过ul:100%,顿时恍然大悟,将其改为和设计稿对应尺寸的rem布局瞬间恢复正常。外层盒子尽量不要设置宽高,因为你无法肯定它的大小,尽量使它的宽高是被元素撑起来的。
- 创建PostPaid卡片栏
- 框架问题
在看见卡片的设计稿时,我有一点点不知道从何下手,它的盒子该怎么嵌套,我开始的想法:
但是我很快否定了,这样会有一些地方不好对齐,实现难度增加,在询问了师傅的意见后,我的框架改成了:
- div大小问题
在开发points部分的时候,我遇到了一个非常棘手的问题:
这个问题困扰了我将近一个小时,**为什么div的高度会远远大于内部元素高度(我并没有设置宽高)**,很难解决,我去搜索不知道该怎么搜,后面一起和师傅好好研究了十来分钟,发现是fontsize捣的鬼,我的fontsize默认为50px,方便计算rem,然而这50px的基准值可干扰到了div的高度:
通过上面三个证据可以看到 div高度 = font-size + 元素高度,这下真相大白,有了上网搜索的关键词了,一搜还真让我找到了:
我尝试了第一种:white-space:pre-line,可惜没有作用,于是我使用第二种:在全局公共样式里加上font-size:0px;问题解决了,就在我和师傅说的时候,他说这种做法可以暂时解决问题,但并不是一劳永逸的方法,为什么呢,如下:
所以说这种方法只能做到解决部分div宽高问题,如果全局适用会对后面的代码产生影响,我想要彻底解决问题应该需要将font-size基准值调整到合适大小。
- 今日遗留问题
我的图:
设计稿:
我开发的界面两条小竖线没有对齐,我还没有想到合适的方法加,本来说在存储数字的div右边边框加上border就能实现,但是这个布局用的flex而且单位长度不一样,边框的效果看起来就不是对齐的,我在思考是将div宽度再设置的合理一些,还是加margin什么的,又是今日留下来的小问题!
- 今日完成界面
学习心得与感悟:
- 开发规范:
- 在开发web时大小使用px,但开发移动端项目时大小使用rem;
- 尽量不要给外层盒子定义宽高,以免开发到后期出现问题不好返工;
- 开发中尽量不使用id设计样式,采用class会方便许多,class的定义最好按照功能来,这样哪个元素需要只需要加上class就行,class可以对应多个元素。
- 设计细节
我觉得很难的一点就是前端开发中所有元素大小间距定义都要根据设计稿来,非常非常揪细节,也许偷懒只想复现的时候看起来差不多就行了,但是绝对不能这样子,不能为了偷一时懒不顾项目的安危。
今天的开发进度依旧不是很快,常常会在一些细节地方卡住,导致完成效率不高,终归还是练习的不够多,目前的css不够熟练(等到界面开发完可能就是js不熟练了哈哈),只能勤能补拙,每天做好自己的任务,不会的尽量自己解决,实在解决不了的就问师傅,可以重点培养自己解决问题的能力,加深对知识点的印象。好好写日志就相当于给自己每天的工作记录重点内容,建立好的错题本,是以后开发中必不可少的步骤!(明天要开发的应该要借用到antd-mobile里的swiper,今天浅看了一下,没有太看懂,明天早上再细细研究)
9.16
学习内容:
商城项目实战④
项目名称:NiceDay
技术栈:React + ReactHook + ReactRouter + Redux + Axios +other
计划完成功能:
- 首页展示
- 充值中心
- 选择套餐
- 购物中心
- 商品详情
- 个人中心
- 个人详情
- 登录注册
计划已进行:3 days
今日参考设计稿:(已划分盒模型)
学习困难:
- 创建功能滑动界面
- Tabs+Swiper组合
一开始打算使用这俩组合开发这个界面的时候,没有很熟练,我想要做到既遍历循环生成li,又使li有两排,我的最初想法是一个div内嵌套两个ul,然后每个ul各自遍历长度为4的数组生成2*4的布局,如下:
结果这种方式生成了2*8的结构,于是我求救了师傅…
师傅说我可以创建长度为8的数组,只使用一个ul循环就行,既解决问题又减少代码冗余。事实证明确实可以哈:
最后的整体swiper+tabs长这样:
- tabs标题栏问题
- 下划线问题
由于用的antd-mobile里的组件,所以样式很多都是自带的,并不好修改。比如我没法直接修改它的标题栏选中时的下划线,至少不能用图片直接代替。
自带的:
设计稿:
不过师傅给了我一个非常棒的思路:可以试试用伪元素替换一个图标上去/画一个半圆。因为有图标素材,所以直接使用的伪元素添加图片:
前提是要先隐藏之前的下划线,然后使用伪元素::after放在title下面,再水平居中就可以。
- 大小问题
我之前的底部导航栏+顶部功能栏+卡片界面宽度全部设置的rem大小,突然出现的tabs宽度影响到了整体的布局,虽然他也只有375px的宽度,但是整个盒子都被他撑开了,造成左右出现间隙:
我思考了一会**把之前的所有ul、div只要是设置了rem宽度的全部改成100%**,果然界面恢复正常,不再左右滑动。
- 文字换行问题
在设计图中,可以看到功能名称都是一个单词占据一行,但是我用的span做不到换行:
我想了很久问了师傅,师傅给了我一个解决方案:将ul的flex-wrap设置为wrap,把li的宽度设为min-content:
但是这样依旧没有解决问题,我的排版甚至变得混乱,于是我选择使用p标签框住文字,然后所有改动在p标签内进行,成功实现:(当然最主要的还是flex-wrap: wrap)
- 创建广告图
这个基本上就只需要加一个图片,成果如下:
- 返工之前代码
- 图片尺寸
每个图片都要设置宽/高,给一个就行,因为图片宽高比是固定的。之所以要设置尺寸是因为如果屏幕大小不一样,图片可能在大屏手机和小屏手机上看着效果不一样,统一rem尺寸看起来差别就不会很大。
- 框架设计
这个是师傅看见我的顶部导航栏给我的建议,开发之前研究界面的时候可以把整体框架先设计好,比如我的导航栏是贴着顶部的,加了个margin,但其实在开发中一般会选择在最外层边框加个padding就解决了,结构看着也清晰一些,不会出现margin塌陷问题。
- 今日遗留问题
可以看到我的两个标题之间距离跟设计图上并不一致,我有尝试过修改tabs的样式,比如把padding去掉、把space-around改成space-between或者是修改他们外层div的宽度,但这些都没有效果,起码尝试修改了有一个小时吧,但是还是做不到,毕竟这个东西是封装好的,我暂时还没有解决办法,也许以后会有。
- 今日成果展示
学习心得与感悟:
今天为止就是把首页的页面写完了,还有一些小的细节没写,比如说点击更多会出现下拉菜单,点击菜单栏右侧会出现滑动菜单之类的交互还没有开始写。今天学到了swiper+tabs布局完成标签页的制作,以及一些代码规范,比如上面说的图片尺寸和框架搭建问题,不能想当然的去写,要知道这些规范设计的原因。下一步的计划是想完善未完成的细节问题,在这过程中尽力解决遗留问题,其次就是尝试调用接口去展示数据,而不是像现在一样是一个静态界面。在调用接口时肯定会出现其它问题,就像师傅说的不定长文本过长溢出了怎么办(css解决单行文本溢出),不同长度的文字布局展示间距怎么办,这都是接下来我需要面临的问题。
9.19
学习内容:
商城项目实战⑤
项目名称:NiceDay
技术栈:React + ReactHook + ReactRouter + Redux + Axios +other
计划完成功能:
- 首页展示
- 充值中心
- 选择套餐
- 购物中心
- 商品详情
- 个人中心
- 个人详情
- 登录注册
计划已进行:4 days
学习困难:
- 新增下拉菜单
- Dropdown组件
来自ant design的组件,不过使用起来感觉不是很好,比如刚开始使用的时候效果如下:
我问了师傅,师傅说我的antd样式没有生效,比如点击li后下面没有关于antd的class样式,之后知道应该是由于我的是react项目直接拿脚手架建的,不是用的umi,导致webpack配置没有弄好,需要自己引入antd.css,所以加了一条:import ‘antd/dist/antd.css’;
但是,就是因为加这么一句,导致我其他地方本来写好的地方又开始布局混乱,像这样:
我的所有文字标签都被自动增加了一个和我fontsize一样大的margin,这个地方纠结了至少一个小时,最后师傅和我说是因为antd样式覆盖了全局的样式,我觉得antd这个组件好难用啊,要达到设计图的效果需要费好大功夫,所以我把dropdown组件去掉了,原生写了一个下拉菜单组件,目前是这个样子:
等到我把antd样式覆盖问题解决好了再考虑用antd吧,其实除了样式覆盖问题,由于我这是img,而官方举例采用的都是button,导致这样点击后下拉菜单的样式和位置对我来说都挺难调整的。
- 新增mini功能滑动小程序:
- min-content
之前写上一个hot功能滑动小程序的时候,由于单词很短也需要换行,所以在p标签内加了一个width:min-content,但是写mini功能的时候不需要,不然就变成了这样:
我写的:
设计稿:
在意识到应该是min-content引发的问题之后,我单独给hot功能界面设置了一个class,其他的为默认,这样就解决问题了。
- 今日遗留问题:
今天引发的问题就是antd的样式覆盖了全局样式的问题,以及我的项目每次启动进去的时候都会有横向滚动条,但是刷新几次后又恢复,不知道什么原因,打算抽空仔细看《CSS世界》纠错。这本书很好,强烈推荐给大家,包括它的新版《CSS新世界》。
- 今日完成界面:
学习心得与感悟:
今天也算是特别感受到了什么叫做一台电脑一杯水,一个bug改一天的感觉。今日进度勉强完成,还有些没有完成,不过首页的,剩下的很多功能都是需要接口的,比如根据用户登录状态判断postcard组件的展示状态,包括点击菜单弹出侧边菜单,都是还没有实现的。写前端没有想象中容易,要考虑的地方很多,遇到的问题也有很多,不适配、版本不一致、属性冲突等等,而且还要考虑代码的美观、简洁。明天研究一下webpack的配置,看看能不能解决antd样式问题,接口方面尝试mock模拟数据看看有没有问题。一个项目真的不能是一个人的心血,起码也得是一群人的心血,一个人写可能几个月也做不好一个像样的项目,太多的逻辑需要考虑。明天继续加油。
9.20
学习内容:
- webpack配置学习
- mock数据流引入
学习困难:
- webpack学习
- 学习原因:
前端本可以直接HTML、CSS、Javascript就行了,不过如果要处理文件依赖、文件合并压缩、资源管理的时候就得利用工具来辅助了。以往有常见的模块化工具RequireJS,SeaJS等,构建工具Grunt、Gulp等,新的技术Sass、React、ES6、Vue等,要在项目中使用这些东西,不用工具的话就略麻烦了。
其实简单地说要聚焦两点:模块化以及自动构建。
模块化可以使用RequireJS来处理依赖,使用Gulp来进行构建;也可以使用ES6新特性来处理模块化依赖,使用webpack来构建。但是相对来说webpack会更好一点。
- 核心概念
- entry:webpack用来构建内部依赖图的开始,包含入口起点依赖的模块和库,可以分离应用程序和第三方库入口,常常使用对象语法,我项目的entry如下:
- output:配置webpack输出文件的位置及名称,一般默认值为”./dist”,也就是运行npm build后再项目文件夹下会多出一个dist文件夹,里面就是项目压缩打包后的静态文件,入口可以有多个,但是输出文件只能有一个。我的项目默认设置的output是这样:
- loader:用于对模块的源代码进行转换。loader 可以使你在 import 或”加载”模块时预处理文件。loader 甚至允许你直接在 JavaScript 模块中 import CSS文件。我之前在项目配置less的时候就用到了这个:
- plugins:目的在于解决loarder无法实现的其他功能,webpack 自身也是构建于,你在 webpack 配置中用到的相同的插件系统之上。webpack 插件是一个具有 apply 属性的 JavaScript 对象。apply 属性会被 webpack compiler 调用,并且 compiler 对象可在整个编译生命周期访问。比如我就在**/.(js|mjs|jsx|ts|tsx)$/**这些文件中尝试配置antd来按需加载样式:
- mode:提供 mode 配置选项,告知 webpack 使用相应模式的内置优化。取值有两个,production/development,默认值是 production。分别用 production 和 development 打包,编译的区别如下:
1、development打包后,一些没有依赖的方法、变量、文件会保留;而 production 则会移除。
2、production 打包后,代码会进行压缩,比 development 的文件小。
我的项目自带的根据需要进行取值:
- react中mock的使用:
这个使用方法和在vue中是一样的,所以不是特别困难,不过也是费了一点功夫,我开始以为在react中要export出去才能被组件接收,没想到并不用。
- 在src下创建mock文件夹
- 创建可拦截请求mock数据
- 使用axios进行数据请求
- 获取请求数据并显示到页面
测试的mock数据有20个,每次选取的都是第一个。还没有登录注册,所以目前没有获取用户token。本来也是想将axios二次封装的,但是今天搞了一下午,封装代码以及安装的依赖包有点问题就没实现。我这只是在headerNav组件中使用mock测试,师傅说在实际开发中我的请求要写在home页面里,然后通过props将属性值传递给各个子组件,明天尝试将mock请求数据优化一下写在父组件中,然后这周时间充裕的话将登录注册界面也写写。
学习心得与感悟:
react项目虽然说通过脚手架搭建的,部分webpack已经配置好,但是在需要第三方库的时候还是需要自己手动配置一下,所以今天特地学了一下,虽然看着云里雾里的,但是好歹对webpack的配置结构有了一些了解,等以后开发经验累积起来,跟着项目走看着应该不会那么迷糊了。关于axios二次封装,之前只是会用,但是真的代码出现偏差我都不知道怎么修改,明天要了解一下axios二次封装的原因和原理,自己实现一下封装,然后就是把mock请求放到home界面来,通过父子组件传值(又可以深入学习传值了)显示到页面上。本周计划完成登录注册,熟悉用户token的传入判断。大概率是要多看些教程。
9.21
学习内容:
- redux与connect使用
- axios二次封装及使用
本来说今天做到将数据请求写在父组件中,然后传值给子组件获得。但是父组件获取到了值,传给子组件的props却是空的:
问了师傅,在vue中确实可以直接传递值实现,但是在react没这么简单,需要通过redux+connect才能实现,所以今天学习了redux。
学习困难:
- redux学习
在应用中所有的 state 都以对象树的形式储存在单一的 store 中。惟一改变 state 的办法是触发 action,一个描述发生什么的对象。 为了描述 action 如何改变 state 树,需要用到 reducers。
- store
在我们使用脚手架搭建项目的时候默认是没有store的,它是由我们手动在项目的src目录下创建的一个保存数据的store文件夹。redux之前是使用 createStore,目前是使用 configureStore这个官方API用来生成store。
store 的三大原则:
单一数据源:整个应用的 state 被储存在一棵 object 树中,并且这个 object 树只存在于唯一一个 store 中;
State 是只读的:唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象;
使用纯函数来执行修改:为了描述 action 如何改变 state 树 ,需要定义 reducers。
reducer
简单来说 reducer 是一个纯函数,什么是纯函数?就是相同的输入必定有相同的输出,就叫做纯函数。reducer 作用就是用来改变 store 中的数据。定义reducer需要两个参数,分别是当前需要被共享的 state 以及用于改变 state 的 action。
- action
action我们可以理解为是一种改变数据的触发行为,作用就是通知reducer改变哪一条数据;
- react-redux学习
本来看的是原生redux,但是 redux 在 react 不好使用。React-Redux 是 Redux 的官方React绑定库。它能够使 React 组件从 Redux store 中读取数据,并且向 store 分发 actions 以更新数据。用原生redux和react结合使用的时候,每次都要载入 store,而且派发完 action 修改数据后还要调用 subscribe 去监听,在监听里更新事件,React-Redux 规避了这些麻烦。
React-Redux的三个关键作用
- 通过 Provider 把 state 注入到全局
- 通过 connect 把 state 和 dispatch 注入到当前组建的 props 上
- 响应式
React-Redux 提供一个 connect 方法能够把组件和 store 连接起来,把 state,dispatch 方法,捏合到当前组件上。connect 有两个参数,两参数都是函数,参数在 connect 内部被调用,参数内能拿到state和dispatch。
- axios二次封装及使用
- 思路
首先我们要知道封装的意义就是方便统一管理,后期维护。所以我们主要封装axios:环境的切换、设置请求超时、设置请求头、请求拦截、响应拦截、封装方法。
- 流程
这些功能都可以选择性封装,比如我就选择了设置请求超时、请求拦截和响应拦截:(注意要暴露实例)
然后把所有接口统一放到api.js进行管理:
之后在组件中使用:
学习心得与感悟:
今天理论看完之后,我有去搜索一些小案例,但是不太尽如人意,首先我不希望使用原生redux,其次我需要请求的数据(比如用户名字)不会被修改,不需要用到action,所以看了很多的例子,大多数都是官网那个计数器或者待办事项的例子,和我需要的例子有点区别,在项目里需要模块化,分类管理,所以我还没有研究明白。不过我觉得我要使用redux的话应该要将axios请求使用redux-thunk异步在action里操作,想法是这样的但是还没有实现。师傅说请求可以写在界面里,嗯..我今天还没搞定。原理都知道要实践组合起来怎么那么难呢。。明天早上要写出来,我把需要用的数据归纳一下都写redux里。感觉比vuex难 T^T
9.22
学习内容:
debug React-Redux (也就是找了一天bug
学习困难:
实现react-redux+connect流程:
- 错误版本
- redux架构
- action.js
这里面放修改state里的属性的操作
- reducer.js
这个用来管理actions,相当于actions仓库
- store.js
这里面用来放reducer仓库和state里的变量
- constants.js
这里放常量,这样不会有重复的
- state.js
这里放state中的数据
文件都是分类好的,整理的很好,但是在运行的时候报错,不管我怎么写他都说我的reducer不是一个函数,但是我打印出来的类型又是函数类型,我感觉是哪里有问题。使用的时候也是把provider写在home.js父组件里,connect放在headerNav.js子组件里,一直报错,我研究了一上午,下午的时候向师傅求救。
- 正确版本
- 把store/reducer/actions结合放在一个文件进行测试
- store 使用
- home.js
- headerNav.js
- 使用细节
- 异步请求可以写在页面也可以写在action里,用法可能会有点轻微不一样;
- 使用父子传值会比connect连接便捷一点,可以直接获取父组件的值,不过如果组件嵌套,使用connect能直接获取到store里的值,connect相当于提供一个方法使所有组件可以直接连接到仓库;
- 使用父子传值时需要在父组件引用子组件的时候传递一对键值,如**
;在用connect获取值的时候使用不需要键值,props里会自带,因为mapStateToProps**已经解决了。
学习心得与感悟:
今天和师傅找了很久bug,哈哈找的最久的就是最开始报错说我写的action函数不是函数,我打印了一下确实是undefined,最后发现是reducers属性我少打了个s,真的蛮搞笑的T_T 。新版本不支持createStore,但是很多教程里都是使用的createStore,应该使用configureStore,但是这个的用法和createStore有点区别,所以也报错。找到configureStore问题之后,好像都会搭配使用createSlice,这两个与createStore相比首先来源不一样,createStore来源react-redux,configureStore来源@reduxjs/toolkit,configureStore封装了一些东西,简单来说就是使用起来会比较方便,毕竟更新不能往复杂了更吧。明天完成登录功能,尝试token的用法。
9.23
学习内容:
商城项目实战⑥
项目名称:NiceDay
技术栈:React + ReactHook + ReactRouter + Redux + Axios +other
计划完成功能:
- 首页展示
- 登录注册
计划已进行:5 days
今日完成界面:
由于只是想写一个测试用的登录,所以就没有写成各个组件了,全部写在login.js一个文件里。
学习困难:
- 输入框输入无效问题
因为我引入antd会导致覆盖全局样式,所以用的是原生表单,修改样式写的,但是遇到了一些小问题:首先是报警告说我没有写onChange属性,看到后我就加了,但是name、value都是默认的;之后就是输入文字但不显示,我在网上没有搜索到答案,问了师傅后,师傅说因为我写了**value=””**,这个表示实时更改,只要input输入框内容发生变化就会自动替换为value,也就是空的,所以我输入什么内容都失败。
- 底部导航栏问题
我之前写的bottomBar组件使用的fixed定位,所以为了可以在每个页面都显示底部栏,就把他放在了router.js里面,但是这样子登录注册界面也会有导航栏,这个问了师傅,说是要写两套不同布局,但我还没搞明白,暂时遗留问题,尽量这两天解决。
- 获取token问题
- 新增username&&userpwd数据,初始值为空,绑定store
- 在actions.js的setLogin方法中添加异步数据请求,设置token
- 解析token
- 页面判断用户输入
- 页面刷新数据消失问题
学习心得与感悟:
今天写页面写了四五个小时,解决token问题不是很顺利,看到的教程有自己写后台接口的哈哈,但是我这只能自己模拟,所以麻烦了一点,主要解决就是路由传参,不过我觉得可能我的路由实现方式不是太好,这两天看看能不能改成适合项目使用的方式,还剩下的底部导航栏问题,可能就是由于不适合写在路由里。我看了clp项目的源码,暂时没找到入口文件在哪,和我的好像有点差别,所以也无法借鉴这个项目来更正。这两天目标是解决导航栏问题,深入学习下现在的多种路由实现及跳转方式。
9.26
学习内容:
pageBuilder项目源码阅读①
学习困难:
- 检验eslint
师傅今天给我他正在做的一个pageBuilder项目,让我阅读学习。师傅怕我这边eslint报错,更新完develop在gitlab拉取代码检查eslint,码一下流程:
- 创建分支
- git checkout develop:切换到远程主分支
- git pull:拉取远程仓库主分支的代码
- git checkout yh:切换到远程自己的分支
- git merge develop:合并远程主分支和自己分支代码
- git push:将代码上传到远程仓库自己的分支上
发现了一些代码轻微飘红,但不影响项目运行:
- 项目难点
如图是此次项目pageBuilder的编辑界面,我们可以拖拽左边的小组件放到中间的展示容器中,通过修改右侧的组件属性值将其打造成自己喜欢的风格。
- 实时渲染
在右侧修改属性值的时候,数据会进行实时更新,展示容器根据更新后的数据进行实时渲染。
- 数据传递
在用户选择保存或者发布时,将其更新改造后的展示容器内的组件属性存储到redux中,以JSON数据的形式传递给APP进行接收然后展示在客户端。
- 组件拖拽
左侧的拖拽用法采用react-dnd实现,舍弃了Drag and Drop。
- 阅读顺序
因为这个项目,现在文件目录是有点混乱的,多少有点找不到想看的文件在哪,所以问了师傅该怎么阅读。平常上手项目:目录结构->依赖安装->路由跳转->项目运行。师傅建议我这个项目首先看路由,然后把主界面也就是pagelist看一下,之后就是看编辑界面,编辑界面又分为左中右三个部分,左边每一个小图标都是一个独立组件,中间容器负责展示效果,右边的每个子项也都是独立组件,可重用性很高。重头文件就是右边部分以及数据源,也就是每个组件都存储的属性值,很长的一个文件,暂时还没看完。
学习心得与感悟:
今天和师傅沟通了很久,了解了这个项目的受众群体、系统操作、项目难点等等,师傅说这个项目和普通react项目还是有点区别的,毕竟类似于构建一个低代码平台,要求实现用户更改组件属性时,代码内的数据也要相应修改,而且在修改属性之后,要在页面上实时地渲染出来,在vue中实现可以使用监听数据变化,只要数据改变就重新渲染。但在react中没有那么简单,react非常依赖于redux,如果没有redux,也许react就像一只没有翅膀的小鸟,失去了灵魂。不仅要求实时更新与渲染,还需要将用户修改过后的数据存到redux,通过redux存储json数据,最后传递给APP,但是在用户点击返回时,数据恢复成原样,只有在用户保存或者发布的时候,才将数据存储到redux中共享。所以现在纠结的是在用户正在修改属性值的时候,数据要如何保证能够在用户边修改边传递给容器用于展示效果,而不会影响到redux中数据变化。我从今天开始就负责阅读这个项目文件写写注释、充当测试–发现bug的时候给师傅提出来、后期对项目比较了解之后开发一些小组件等。
9.27
学习内容:
pageBuilder项目源码阅读②
学习困难:
- pageList
主界面中内容列表的文件,使用antd的组件重写样式,包含分页、点击展开、编辑、发布、预览等功能
- async&&await
看到这里。有关async和await的知识点有点记不清了,于是去学习了下。
总结起来就是,async用于声明一个函数为异步处理函数,除此声明外,函数的写法和同步函数没什么区别,它返回的是一个Promise对象,而Promise是用来干嘛的,他是用于解决异步问题(比如回调地狱)的异步操作的对象,它本身不是异步的,你使用它的时候他会立即执行,但是他会等到你需要的结果才会返回结果;而await意味着等待一个异步函数的执行结果,它必须写在async内部,也就是暂停当前async的语句执行,让async先等一下,等到await后面的异步函数执行完后,再继续执行语句。
比如上面截图的执行顺序为先进入async函数执行对象赋值,然后遇到await,等待doFetchDataPrivList(data)执行完成返回结果后,async剩下的语句才会执行。这种等待方式又让我想起来宏任务和微任务的执行顺序,比如setTimeout是宏任务,而promise的链式调用内容是微任务(promise本身是立即执行的!),在同步代码中,如果遇到promise的.then方法,将其压入微任务栈,遇到setTimeout压入宏任务栈,在同步代码执行完之后,主栈中没有可执行代码时,先执行微任务栈的可执行代码,然后才执行宏任务栈的可执行代码,如果在执行微任务栈的代码时又有立即执行函数或者同步代码,依旧先执行同步代码,再执行微任务,最后执行宏任务。
- useAsyncFn
这个项目的接口请求全部采用这种方式创建,在请求操作方法外层套了一层来自react-use的useAsyncFn钩子用于异步函数的状态管理,相当于react使用redux进行状态管理。useAsync返回的是请求结果及状态,而它返回的是请求函数并非请求结果,需要手动调用的时候再请求。比如下图例子,返回的就是名为doGroupList的请求函数。
- 运算符知识: ?. && ?: && ?? && ``
在看到pagelist分页函数的时候,发现了一个不知道的es6小知识点
?. 意思是可选链,类似 && ,使用场景:在开发中对接口返回的嵌套对象数据进行属性判断,比如 res ?. data ?. pageList 可以写成 res && res.data && res.data.pageList 或者 res ? (res.data ? res.data.pageList : undefined) : undefined,挨个判断属性是否存在。
?: 意思是可选参数,比如data { id: string | number , key ?: string} 表示key为可选属性,相当于参数自动加上 undefined。
?? 意思是取默认值,类似 || ,使用方法:const res = actualValue ?? defaultValue 也就类似于 actualValue ? actualValue : defaultValue。
`` 比较方便,可以在反引号内部直接写代码,可操作性高了。
学习心得与感悟:
今天看的pagelist文件夹业务逻辑还是比较好理解的,没有涉及到很多像redux一样有好几个不同管理的文件,主要操作就是获取数据、分页、检查权限、接口请求等。整个主界面列表使用的是antd的pageForm、Table等,还有一些自定义组件的使用,明天开始操作界面的代码阅读!
9.28
学习内容:
pageBuilder项目源码阅读③
学习困难:
- components/editor/schema/edit/customerComponents.tsx
该文件主要用于存储属性数据,用户可以进行相应修改。
比如我在页面上添加了一个Grapic Article组件,我可以对这个组件进行位置调整、添加图片、添加备注信息等修改,这些修改在用户点击保存或发布时将会被存储到customerComponents中,以便后续传递给APP接收。比如下图就展示了GraphicPost组件的属性,其中header、basicInfo等属于属性的归属,比如title属性就归属于basicInfo,其中 title 也是以公用组件的形式创建的,以key的形式传递给schema,增加代码复用性,提高开发效率。
- components/editor/schema/fileds/index.tsx
存储的主要是菜单以及对应左侧内容的数据。比如下图,点击PTO之后左侧可拖拽组件内容会发生改变。
其中DragPanel中的data数据就是组件的所有属性数据,用于发送给APP。
- components/editor/Left/index.tsx
左侧容器文件:根据菜单图标激活状态判断,显示展示内容:
- components/editor/Left/DragItem.tsx
- 类型标记
看到函数参数之间的冒号,有点疑惑,搜索发现是ts语法,表示类型标记,比如:(args : number)就代表args被标记为number类型。
- any & never & void
没有见过的语法,上网搜了一下发现是ts中的类型,除此之外还有never、void都是TS类型。
any:在一些情况下,如果我们无法确定变量的类型时(或者无需确认类型时),我们可以将其指定为 any 类型。,对于标记为 any 类型的变量,不需要进行类型检查,直接通过编译阶段。特点:1. 允许赋值为任意类型; 2. 允许访问任意属性和方法; 3. 在声明变量的时候,如果没有指定变量类型,会被识别为 any 类型。
never:表示永远不会有值的一种类型,比如 never 类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型。适用于:比如在swtich场景中,我们通常会在结尾添加一个default,但实际上流程进入default就意味着场景已经出错了,所以我们可以在 default 中写 const check : never = type; 及时发现问题。
void:表示没有任何类型,申明为 void 类型的变量,只能赋予 undefined 和 null。
- useDrag(☆)
useDrag提供了一种方式,以让组件成为DnD系统中的拖拽源。通过传递特定的参数,提供需要传递给放置组件的数据(即item),组件渲染所需的props(即collect),以及等等。
返回值:
isDragging:collecting function的返回值。如果没有collecting function会返回空对象,判断是否在拖拽中,替代原本判断拖拽中的方法。
dragRef:拖拽对象的connector函数。
connectDragPreview:拖拽预览的connector函数。
collect: 会返回一个对象的函数(collecting function),拥有monitor参数,返回的对象会注入到当前组件中。
通过 isDragging 判断是否在拖拽中,dragRef 绑定拖拽的 item, connectDragPreview 应该是拖拽时的跟随图像:
学习心得与感悟:
今天主要阅读了组件的属性数据、左侧容器的拖拽组件的实现以及一些小的ts知识点。拖拽由子组件使用useDrag实现,猜测父组件(也就是中间容器)应该会是useDrop,useDrag返回三个值,接收之后用包含子组件的div接收绑定,拖拽的组件还有数量限制,需要判断数量、拖拽的状态。目前为止看的代码都能理解,但是需要自己编写的时候应该也没那么容易,可能要费一番功夫思考逻辑以及搭建过程。
9.29
学习内容:
pageBuilder项目源码阅读④
学习困难:
- components/editor/canvas/index.tsx
这个文件主要构建的是组件预览区域:
经常在文件中看到 interface xxx,本来以为可能和 java 开发中的 interface 一样,但看它的实现形式和java还是有很大区别的。java 中的 interface 一般里面都是函数体的定义,用于实现类的多继承;但是 ts 中的 interface 用于对值所具有的结构进行类型检查。
昨天的代码阅读中,看到左侧的小组件每个都绑定了可拖拽属性,使用到useDrag,它们的统一目标就是拖动到中间容器中,所以中间容器需要绑定useDrop。useDrop提供了方式,以让你的组件作为放置对象存在于dnd系统中。通过传递指定参数,以指定接受(accept)的拖拽对象,收集(collect)的属性,等等。第一个返回值为collecting function的返回值,第二个返回值为放置对象的connector函数。其中canDrop、isOver在connect(monitor)进行监听是否可以放置、是否重叠等,同时该容器设置了添加拖拽组件和移除拖拽组件功能。
当拖拽组件和放置容器重合了&&此处可以放置,如果是已经在容器中的组件,拖拽到可放置的位置会出现插入的线条,用于已经放进来的组件调整位置;如果是第一次进容器的组件,拖拽到可放置的位置会出现一个提示盒子:drag and drop here,提示用户可以放置于此。
为了在整个编辑页面都能使用拖拽功能,在editor/index.tsx文件中添加了DndProvider进行页面包裹,类似于Redux中的Provider。
判断用户有没有修改容器的背景属性,比如颜色、尺寸等,如果修改了就使用修改值,没有修改就使用默认值。
看到一个没见过的用法,所有元素的className都写成styles.xxx的样式,问了一下师傅,这是css modules:按需加载,不会造成样式污染,是优化加载的一种方法,局部作用css样式的原理是因为在使用styles.xx之后会自动生成一个独一无二的名字,不会与其他选择器重名。比如在某组件的less文件中设置 .title的样式,在使用 styles.title 之后,在检查元素时发现变成了 ._src_Header_Module_title,完美避免了父组件引用这个组件时,父组件的styles里面也有同名.title从而覆盖子组件样式。
- components/editor/canvas/canvasItem.tsx
这个文件描述组件拖拽到在中间容器之后的,展示在中间容器上的组件。主要描述了移除、调整、修改该组件的功能,将其与右侧属性相绑定,右侧实时更改,这边就会更新渲染,连接到store保存属性更改。由于canvasItem既需要被拖拽又需要放置,所以在这个文件中useDrag && useDrop都有使用,除此之外还使用了useRef、useAppSelector、useAppDispatch等。用法基本都一样。
学习心得与感悟:
今天阅读了中间容器的文件,基本熟悉了到 react-dnd 的用法,还学习到了 css modules 优化加载,ts 类型检查等。react-dnd 主要 api 就三个,一个设置拖拽源为可拖拽,一个设置放置源为可放置,一个设置区域为允许拖拽放置,可能我的理解有些偏差,我还不太理解 useDrop 到底是作用于拖拽组件上还是放置容器上,我本来的理解是在容器上,但是我又看到 CanvasItem 也使用了 useDrop,我也没找到怎么能把两个组件重叠在一起,所以我对 useDrop 作用的理解就有点开始混乱了。也可能是这个地方有些功能我还没了解到,明天再深入摸清楚这个系统的细节功能。
9.30
学习内容:
pageBuilder项目源码阅读⑤
学习困难:
- components/editor/Right/index.tsx
该文件主要描述编辑界面的右侧属性操作容器,涉及非常多的数据交互与传递。
开局遇到一个深度遍历的递归函数 traverse(data,fn) ,用于遍历监听所有子值变化。猜测应该用于用户修改属性时,如果发现属性值被修改,就要立即响应,应该类似 diff 算法吧?深度第一遍,从根到叶,子节点按相反的顺序,如果fn返回错误,则终止遍历。
这段函数用来渲染字段,比如说 Graphic Post 组件中属性可能具有 添加标题、插入图片、调整位置等,每一个修改的功能都写成一个小组件,将这些属性小组件存放在界面组件中,传入renderField函数中,renderField函数根据传入的类型遍历渲染,完成右侧属性修改容器的内容填充。
已经搭建好的草稿是可以复制的,复制过来的page一般都要重新设置起始时间以及失效时间。
初始化字段的时候将内容都清空重置,等待用户输入。
用来归纳相似属性的折叠面板渲染,比如说title、Edit ‘More’等等属性都归属于 Basic Information,而Basic Information 可以折叠与展开,这个函数就是来绑定折叠面板的header 和 content。
学习心得与感悟:
今天主要阅读了右侧属性面板的index文件,学习到了数据与组件属性相绑定、不同的组件如何遍历渲染构建,判断逻辑找到清空时机等。接下来的阅读目标是:
- 把右侧的界面组件观看,学会两者之间的连接;
- 知道添加一个新拖拽组件需要的所有流程;
- 尝试添加新组件在本地查看。
10.8
学习内容:
pageBuilder项目源码阅读⑥
学习困难:
如何实现新组件的添加
- 添加组件属性
根据菜单分类,如果新建组件在CLP内,则将属性添加在customComponents里
新建一个组件,需要在editFields中添加组件名及内部属性。比如Toolbar组件,header属于一个属性类别,里面包含自定义属性类,sideMenu属于BasicInfo管辖范围内,使用的是Switch类,根据按钮决定是否展示。不过这个地方已经更新了,尝试注释,结果发现对界面无影响,问师傅说已经重写在render里了,比如BasicInfo重写在renderBasicInfo里,而renderBasicInfo包含renderToolbar,相当于提取出来了。
- 封装组件属性类别
目的是提高属性重用性,在将每一个属性种类进行整体封装。先去获取当前版本的数据大json,然后根据type进行分类渲染组件,下面就是根据具体字段渲染具体属性值。比如下面就是轮播图组件的basicInfo封装。
- 定义左侧组件名、图标、数量等
左侧面板渲染组件时,遍历menus数据,menus中包含DragPanel,存放拖拽组件的容器,涵盖了拖拽组件所需的所有属性数据,传递给DragItem。也就相当于将组件名称、图标、数量全部传递给左侧面板。
- 创建中间画布
中间面板,用于预览修改效果,通过canvas实时获取的组件属性实时渲染效果,这个相当于平常写独立组件,‘画’界面。
学习心得与感悟:
非常默契!今天本来也打算看添加新组件所需的步骤,刚好师傅也来让我阅读。不过这个阅读起来还是没有那么简单,还是咨询了师傅关于添加新组件的流程。如上所示,然后师傅说等一阵子可能有个新需求,一个简单的组件可以让我练练手,目前的话就是我明天开始自己写写小demo,仿写一下之前的组件之类的进行项目实战!
10.9
学习内容:
pageBuilder项目源码阅读⑦
学习困难:
实现添加demo组件(参考Toolbar)
- 添加属性
首先在schema/edit 的customComponents文件中插入新组件 Ydemo 所需的属性:
- 属性布局
由于目前暂定是给每个组件改写basicInfo内容,所以要在right/renderBasicInfo中新建一个名为 renderYdemo 的文件,内容为想要的basicInfo内容。如果只是添加了属性是不够的,仅仅是提供了数据,不能在页面上展示。
- 定义组件
要将组件数据传递给左侧面板,需要在schema/fields中的 customComponents 文件里添加 Ydemo 的组件名、数量、图标等,还有 Ydemo 中属性的详细数据,比如说我有 messageBox 属性,那么我就还需要 messageBoxId 写在里面,这里的数据会传递给左侧面板以及 renderYdemo 中,比如 basicInfo 中遍历获取属性名就需要用到这里的数据。这里的数据形式为json格式,便于获取和展示数据。
- 展示组件
用于中间面板展示组件的,写法类似于之前写独立组件的方法。在 schema/preview中的 customComponents 文件中加入 Ydemo 的方法并导出,在这里面写 Ydemo 的样式:
- 成果展示
学习心得与感悟:
今天的任务完成的不是特别困难,但是有一个小问题,关于edit添加属性,如果在edit中没有添加belongTo:‘basicinfo’,在右侧面板中 basicInfo 是空白的,不会展示出来,但只要添了一个属性属于 basicInfo,不管添加的是什么属性,都能够成功展示重写后的 basicinfo 。暂时没弄明白是什么原理,应该所有属性重写都这样,目前是需要添加一个属性给它声明出来,才会展示。
10.10
学习内容:
pageBuilder项目之拓展学习——React-dnd
pageBuilder项目有拖拽组件功能,用到的第三方库为react-dnd,现在来深入学习一下概念和用法。
学习困难:
- 概念
React DnD 是一组 React 高阶组件,使用的时候只需要使用对应的 API 将目标组件进行包裹,即可实现拖动或接受拖动元素的功能。将拖动的事件转换成对象中对应状态的形式,不需要开发者自己判断拖动状态,只需要在传入的 spec 对象中各个状态属性中做对应处理即可。
Manager
Manager是DnD中非常重要的角色,内部保存了store、monitor和backend,Manager作为入口,对外暴露三者,会订阅store,并调用Backend的setup(负责注册拖拽相关一系列的事件)或teardown(销毁拖拽事件)。
Backends
React DnD 抽象了后端的概念,我们可以使用 HTML5 拖拽后端,也可以自定义 touch、mouse 事件模拟的后端实现,后端主要用来抹平浏览器差异,处理 DOM 事件,同时把 DOM 事件转换为 React DnD 内部的 redux action。主要注册了拖拽相关的监听器,对外提供了dom(drag、preview和drop)注入方法。
Item
React DnD 基于数据驱动,当拖放发生时,它用一个数据对象来描述当前的元素,比如 { cardId: 25 }。
Type
类型是唯一标识应用程序中整个项目类别的字符串(或符号),类似于 redux 里面的 actions types 枚举常量。
Monitors
拖放操作都是有状态的,React DnD 通过 Monitor 来存储这些状态并且提供查询。
Connectors
Backend 关注 DOM 事件,组件关注拖放状态,connector 可以连接组件和 Backend ,可以让 Backend 获取到 DOM。
useDrag
用于将当前组件用作拖动源的钩子。
useDrop
使用当前组件作为放置目标的钩子。
- 使用
- 使组件能够被拖拽(DragSource):
使用 DragSource 包裹组件,使其可以被拖动。
参数讲解:
- type: 必填。字符串,ES6符号或返回给定组件的函数props。只有为相同类型注册的 drop targets 才会对此拖动源生成的项目做出反应
- spec:必填。一个普通的JavaScript对象,上面有一些允许的方法。它描述了拖动源如何对拖放事件做出反应。
- collect:必填。收集功能。它应该返回一个普通的对象注入你的组件。它接收两个参数:connect和monitor。
- options:可选的。一个普通的对象。
spec 对象中的方法
- beginDrag(props, monitor, component):必填。当拖动开始时,beginDrag 被调用。您必须返回描述被拖动数据的纯 JavaScript 对象。您返回的内容会被放置到 monitor.getItem() 获取到的对象中。
- endDrag(props, monitor, component):可选的。当拖动停止时,endDrag 被调用。对于每个 beginDrag,endDrag 都会对应。
- canDrag(props, monitor): 可选的。用它来指定当前是否允许拖动。如果您想要始终允许它,只需省略此方法即可。注意:您可能无法调用monitor.canDrag() 此方法。
- isDragging(props, monitor): 可选的。默认情况下,仅启动拖动操作的拖动源被视为拖动。注意:您可能无法调用 monitor.isDragging() 此方法。
方法中的参数 props, monitor, component
- props:当前组件的 props
- monitor:一个 DragSourceMonitor 实例。使用它来查询有关当前拖动状态的信息,例如当前拖动的项目及其类型,当前和初始坐标和偏移,以及它是否已被删除。
- component:指定时,它是组件的实例。使用它来访问底层DOM节点以进行位置或大小测量,或调用 setState 以及其他组件方法。isDragging、 canDrag 方法里获取不到 component 这个参数,因为它们被调用时实例可能不可用
collect 中的 connect 和 monitor 参数
- connect: 一个 DragSourceConnector 实例。它有两种方法:dragPreview()和dragSource()。
- dragSource() => (elementOrNode, options?):常用方法,返回一个函数,传递给组件用来将 source DOM 和 React DnD Backend 连接起来
- dragPreview():返回一个函数,传递给组件用来将拖动时预览的 DOM 节点 和 React DnD Backend 连接起来
- monitor:一个 DragSourceMonitor 实例。方法如下:
- 使组件能够放置拖拽组件(DropTartget):
使用 DropTarget 包裹组件,使其对拖动、悬停或 dropped的兼容项目做出回应。
参数讲解:
- type: 必填。字符串,ES6符号或返回给定组件的函数props。此放置目标仅对指定类型的 drag sources 项目做出反应
- spec:必填。一个普通的JavaScript对象,上面有一些允许的方法。它描述了放置目标如何对拖放事件做出反应。
- collect:必填。收集功能。它应该返回一个普通的道具对象注入你的组件。它接收两个参数:connect 和 monitor。
- options:可选的。一个普通的对象。
spec 对象中的方法
- drop(props, monitor, component): 可选的。在目标上放置兼容项目时调用。可以返回 undefined 或普通对象。如果返回一个对象,它将成为放置结果,可以使用 monitor.getDropResult() 获取到。
- hover(props, monitor, component): 可选的。当项目悬停在组件上时调用。您可以检查 monitor.isOver({ shallow: true }) 以测试悬停是仅发生在当前目标上还是嵌套上。
- canDrop(props, monitor): 可选的。使用它来指定放置目标是否能够接受该项目。如果想要始终允许它,只需省略此方法即可。
- 文档没有提供按目的处理进入或离开事件的方法。而是 monitor.isOver() 从收集函数返回调用结果,以便我们可以使用 componentDidUpdateReact 钩子函数来处理组件中的进入和离开事件。
方法中的参数 props, monitor, component
- props:当前组件的 props
- monitor:一个 DropTargetMonitor 实例。使用它来查询有关当前拖动状态的信息,例如当前拖动的项目及其类型,当前和初始坐标和偏移,是否超过当前目标,以及是否可以删除它。
- component:指定时,它是组件的实例。使用它来访问底层DOM节点以进行位置或大小测量,或调用 setState 以及其他组件方法。canDrag 方法里获取不到 component 这个参数,因为它们被调用时实例可能不可用。
collect 中的 connect 和 monitor 参数
- connect: 一个 DropTargetConnector 实例。它只有一种 dropTarget() 方法。
- dropTarget() => (elementOrNode):常用方法,返回一个函数,传递给组件用来将 target DOM 和 React DnD Backend 连接起来。通过{ connectDropTarget: connect.dropTarget() }从收集函数返回,可以将任何React元素标记为可放置节点。
- monitor:一个 DropTargetMonitor 实例。方法如下:
- DragDropContext & DragDropContextProvider
使用 DragSource 和 DropTarget 包裹的组件,必须放在: DragDropContext 包裹的根组件内部,或者 DragDropContextProvider 根标签的内部。
DragDropContext
使用 DragDropContext 包装应用程序的根组件以启用 react-dnd。
参数
- backend:必填。一个 React DnD 后端。除非您正在编写自定义的,否则建议使用 React DnD 附带的 HTML5Backend。
- context:backend 依赖。用于自定义后端的上下文对象。例如,HTML5Backend可以为iframe场景注入自定义窗口对象。
DragDropContextProvider
作为 DragDropContext 的替代方法,您可以使用 DragDropContextProvider 元素为应用程序启用React DnD。与 DragDropContext 类似,这可以通过 backendprop 注入后端,但也可以注入一个 window 对象。
参数
- backend:必填。一个 React DnD 后端。除非您正在编写自定义的,否则建议使用 React DnD 附带的 HTML5Backend。
- context:backend 依赖。用于自定义后端的上下文对象。例如,HTML5Backend可以为iframe场景注入自定义窗口对象。
- 实战小demo
- 创建新项目:npx create-react-app dnd-demo
- 配置项目:
- npm run eject
- 在 babel 中添加 plugins 配置
- 在最外层容器 App.js 中添加:
- 创建中间组件:
- 创建核心组件:
学习心得与感悟:
今日实现基础react-dnd的实现,React DnD 使用数据而不是视图作为事实的来源。在屏幕上拖动某些内容时,不会说正在拖动组件或DOM节点,而是说正在拖动某种类型的组件。一共有三个组件具有相关使用,分别是最外层容器、中间件Container(数据源和数组交换)、和核心组件Card(使用react-dnd的api)。除此之外,还有react-dnd的相关扩展用法:
- 让组件既可以被拖动也可以接收拖动元素
(1)使用useRef引入ref:ref = useRef(null)
(2)使用drag和drop包装ref:drag(drop(ref))
(3)将ref变量传给组件
- drag组件传递数据
(法1)直接使用item的属性传:item:{type:’Card‘,id:1}
(法2)使用begin方法传值,begin方法的返回值会覆盖item属性,一定要传type属性
drop组件可以在hover或drop方法中的第一个参数获取到,或使用DropTartgetMonitor的getItem()函数获取
- 获取drag组件或drop组件的状态信息,如isOver,isDragging
drag:collect函数返回的对象会成为useDrag的第一个参数collectProps,可以在组件中直接使用
drop:collect函数返回的对象会成为useDrop的第一个参数collectProps,可以在组件中直接使用
10.11
学习内容:
pageBuilder项目之拓展学习②——React-beautiful-dnd
学习困难:
- 结构
- DragDropContext:构建一个可以拖拽的范围,即最外层包裹拖拽区域的wrap;
- Droppable:某个包含若干个可拖拽元素的组;
- Draggable:可以被拖拽的元素。
- 使用
- 引入 react-beautiful-dnd: npm i react-beautiful-dnd
- 我的源代码结构:
- 按照上图改成用拖拽包裹之后:
可以看到,在最外层的 my-demo 盒子外部套了一层 DragDropContext 作为容器;Droppable 在包裹的map的 demo-list 之外;最内层则是每一个可以被拖拽的元素实例 list ,被 map -> Draggable -> list 层层包裹。
- 注意
- DragDropContext 可选5个函数作为参数
onBeforeCapture / onBeforeDragStart / onDragStart / onDragUpdate / onDragEnd,其中用户拖拽改变的结果一定要写在 onDragEnd 中,传递改变后的 state 给 react,否则拖拽在松开鼠标后会恢复到拖拽之前的顺序。比如在 onDragEnd 中写重新排序函数,最重要的就是 source + destination,里面有我们原来的位置和拖拽之后的位置,利用这两个index拿到源item并调整目标位置。
- Droppable 和 Draggable 结构的 props
- 第二层结构需要添加 droppableId 和 方向,水平为horizontal,竖直方向为vertical
- 第三层结构需要添加 draggableId 和 index 以及 key,这几个都是用于区分不同项的手段。
draggableId={item.name} index={index} key={item.name}
- Droppable和Draggable结构内包的 箭头函数 及 div内的参数
二层和三层包裹的必须是一个返回原本html结构的函数, provided是官方提供的参 数,里面包含着拖拽一系列参数,事件监听等。
- 第二层 div:
ref={provided.innerRef}
{…provided.droppableProps}
- 第三层 div:
ref={provided.innerRef}
{…provided.draggableProps}
{…provided.dragHandleProps}
- 在map结束到 Droppable结束 之间的位置放一个 {provided.placeholder},用于为拖动的元素占位。
- 报错:Unable to find draggable with id: xxx
报错原因是因为在 index.js 中 React18 采用了严格模式(<React.StrictMode>),将其删除就可以拖拽啦。
学习心得与感悟:
昨天学习的 react-dnd 与今天学习的 react-beautiful-dnd 虽然都是制作拖拽组件的三方库,但是却有很大的不同。首先就是API完全不一样,比如 react-dnd 主要用的是 DndProvider、useDrag、useDrop,而 react-beautiful-dnd 主要用的是 DragDropContext、Droppable、Draggable;除此之外,当然就是用法的不一样,react-dnd 相比之下还多了 manager、monitor、collect等等一系列的概念;参数的不同也是他们之间的差别。不过总的来说,我觉得 react-beautiful-dnd 更适合新手,比较容易上手,就是用的人好像不多,踩坑的时候不太好找解决方案;相比之下 react-dnd 的社区可能比较大一点,虽然没那么好上手,但是比较‘专业’。感觉就像 react 和 vue 的区别,一个适合大项目比较好维护、社区人数多,一个适合小项目比较容易上手开发、社区人数少。
10.12
学习内容
修改昨日bug
学习困难
●昨日问题
○unable to find draggable with id: xxx
这个问题出现在控制台,只是一个警告内容,但是我的拖拽功能并没有成功实现,每当我点击拖拽元素的时候都会弹出这样一个警告。使用react-beautiful-dnd的人真的不多,我搜索了很久之后发现解决方法:由于React 18 会自动采用严格模式,也就是在 index.js 中会在外面套一层<React.StrictMode>,导致出现警告无法实现拖拽。
把这个严格模式删除就可以解决问题。
○userList.splice is not a function
前面那个问题解决后,拖拽是可以实现了,但是拖拽后松手时又恢复原状,也就是说没有成功更新拖拽后的状态结果。那我知道问题肯定会出在 onDragEnd:
但是我认真看,好像又没有问题,于是我尝试使用Object.prototype.toString.call()打印 result、userList 等进行调试,得到的是他们都是对象,然后我知道肯定是因为 userList 是对象,对象中没有 splice 方法,userList 是 demoList 深度克隆得到的,但是我的 demoList 是对象数组,是一个数组:
我在控制台打印demoList ,得到的结果是 [object array],那就不应该了,肯定是深度克隆的时候出了问题。果然,由于我这里使用的是默认新建对象,所以克隆后 userList 变成了对象。
这个深度克隆看着不是很好理解,我重写了一个好理解的:
●成功运行
学习心得与感悟
今天没有学习新知识,下午开始改bug,解决最久的bug是第二个,其实在我个人使用感受来说,react-beautiful-dnd 虽然使用人数少,不太好找一些bug的解决方法,但是它毕竟是封装了一点react-dnd的,所以使用起来肯定更简单,看起来也更美观,更符合页面的布局。像 react-dnd 的话可能需要自己写方法才能使拖拽如此丝滑,但是 react-beautiful-dnd 却是自带的,相比之下着实方便。
10.13
学习内容:
pageBuilder项目源码阅读⑧
学习困难:
- 可视化楼层文件阅读
- components / editor / Floor
- index.tsx
通过 expanded 状态判断用户是否点击:取消展开,如果点击就只显示展开按钮,否则显示整个楼层。默认为 TRUE。
- components / editor / TreePanel
这个文件夹的文件主要是针对拖拽过来的组件,可以将其缩略图标及组件名称以楼层形式展现在画布右侧。
- index.tsx
首先将中间画布绑定state数据,其中codeTree是一个 reducer,包含一些选中的标签、id等属性。
绑定 useDrop,使画布具有放置作用,楼层内展示的组件可以拖拽调整组件在画布中的位置。
通过 children 属性,也就是传入的 TreeItem 个数进行遍历展示到界面。
- TreeItem.tsx
楼层中如果有 Toolbar ,那么它只能在第一层,所以在这个地方写了有关dataType的判断,不是 Toolbar 的组件才可以进行移动调整楼层。
设置拖拽源,绑定parentId,即组件id:
设置拖拽样式,当拖拽的时候占位透明度变为50%,提示用户目前拖拽的元素原始位置:
判断当前位置是否能够放置组件,保证当前位置不属于 isParentNode 的返回标签,就在此生成一条蓝色的线条,代表插入位置线:
学习心得与感悟:
今日阅读的组件有点像 左边面板+中间画布 的组合,因为他既要拖拽也要放置,还需要调整位置,但是整体来说难度并不是很大,pageBuilder了解了拖拽与数据交互之后,所有的文件都大同小异,没有什么特别难的地方,最主要的就是整体的贯通逻辑线,在开发时要做到连成一条线,要有头脑、有设计、有思路的开发。
10.14
学习内容:
pageBuilder项目源码阅读⑨
本来师傅说给一个时间不紧的小单子给我练练手,但我没申请SVN权限,今天申请然后下周开始做,今天就先了解一下数据与视图的交互流程。
学习困难:
- src / app
可以看到这个文件夹内的命名都是xxxSlice、xxxReducer,这些文件存储的都是状态管理数据,用于传递给画布视图、App方面。其中最主要的数据都存储在 codeTreeSlice 文件中。
- codeTreeSlice
初始化数据,默认root div下的背景颜色、背景图。
reducers 中包含多个 actions,用于修改数据。
比如 appendCom() 就是当中间画布上出现新拖入的组件时触发,如果当前拖拽的组件有id,即为新增组件,反之则为移动组件。
将 actions 全部打包导出,最后将 reducer仓库整体导出,其他文件就可以引用到这里的actions,从而获取或更新数据。
- Right / render xxx
这些文件夹内有用于当用户通过右侧属性面板修改属性时,及时将修改传递给 reducers 的文件;
比如在之前写的Ydemo中,basicInfo 中的按钮可以给组件右边添加图标,就采用到:
当用户按下按钮时,handleChange()会使 ‘N’ –> ‘Y’,得到按钮值变化,下面采用 toolList 去接收focusComponent?.props.extraTool,将它解析成json数据后,再通过 toolList 判断 checked 值,如果确实点击了,将props.extraTool 中的数据也统一改变,最后使用handleChange() 提交修改。它的返回值如下
在 Ydemo 为中间画布创建的预览文件中,在 rightRender() 中判断按钮值来决定组件的右侧需要显示什么图标(这是只点击一个按钮的情况):
使用 getMegSrc() 判断需要展示的图标类型:
最终点击 MessageBox 按钮之后得到的预览效果如下:
学习心得与感悟:
其实 pageBuilder 项目和平常项目的数据交互有点不太一样,平常的项目通常将数据统一放在models文件夹下,像这样(这个应该是umi框架搭的):
但是 pageBuilder 不太一样,他偏原生:
阅读起来肯定不比普通项目方便。包括p ageBuilder 的项目文件其实都是有点混乱的,毕竟开发时间久远,开发人员也不多,至此为止 pageBuilder 项目源码阅读应该就到此为止啦!收获挺多的,包括数据交互、数据传递、组件拖拽等等,也学到了挺多东西的!