[sweep-it] 分享我学习和使用React的历程

488 阅读13分钟

前言:几天前我发了两个沸点,是关于我学习和使用React,并创建的一个minesweeper游戏。发现大家挺喜欢的,因此,我写了这一篇文章,以此来记录自己的历程,也希望对大家有一点点帮助。

static image

开始使用React并构建一个小项目

学习一门新的技术,最好的开始就是阅读官方文档,然后才开始自己的实践。因此,我的学习步骤可以归纳如下:

  1. 带着一些思考去阅读React官方文档
  2. 理解React和周围生态,分析create-react-app工程结构
  3. 开始构思一个小小的项目,能够让自己在实践中学习。写项目过程中遇到的问题,有几个解决途径
    • 社区生态:解决状态管理、路由等问题,甚至好的插件(在深入了解之前,建议先用React本身去构建基础UI)。
    • 官方文档:基础概念加深印象。
    • StackOverflow:疑惑解析。
    • google一下(英文),往往有新发现。
  4. 参与社区,了解反馈。做游戏运营或者策划的同学应该很清楚,如果做一件事按游戏的心态去做,成就感会挺高的,并且在这个过程中不断得到反馈,这种反馈又激励你更大的求胜欲望。因此我在项目做得差不多的时候在掘金上发了一个沸点,发现大家挺喜欢这个项目,这也激励我继续写下去。
  5. 源码阅读。这个是终极状态,像Redux这种,代码量少,写得又很棒的库,读一读,收获挺大。比如有心人就会看到我仓库里面有个/test/compose.js,这个文件就是测试一下写写compose函数,想法来源也是Redux

首页开始分析

特点

  • 声明式:设计好UI,它就会按你设计得方式运行,react帮助您有效更新、正确渲染。
  • 组件化:一个组件封装好UI和逻辑(基于JavaScript而非模板),可以轻松地将组件进行组合,以构成更加复杂的页面。
  • 一次学习 随处编写React社区活跃,内容丰富,一种技术满足你服务端渲染和Native应用的需求。

从官方特点介绍,至少可以知道一些要点

  • JavaScript:以js为技术基础来构建UI,如果js基础比较扎实,应该可以较快入门。
  • 组件化:组件化是目前前端开发的主流,浏览器甚至还在推广Web Component技术,这就要求我们思考问题的时候,要经常想一想,一个事物是否足够独立?是否足够小?是否能够满足大多数场景的需要?
  • 生态丰富:学习一门技术,可以快速满足多种应用场景。
  • 状态与DOM分离:前端界长提MVVM的概念,所谓ModelViewModel-ViewReact做到了数据(状态)和视图(DOM)分离。

因此,在学习React之前,我花了一些时间来复习JavaScriptDOM的一些基础知识,后来的学习中证明这种角度是对的。

概览

  • 简单组件:render函数会在页面更新时,被React调用,用于生成一帧。是的我把它想象成一帧页面(以时间长度来看,一次render是页面生命周期的一帧)。至于为什么是一个render函数,因为我们的React是一个functionorclass,使用一个函数来更新页面会很方便区分和保留不同的状态。(Vue里面data声明为一个函数,原理也是一样的。)
  • 有状态组件:使用props来获取从父组件传递来的状态,完成父子组件通信。需要注意的是,React上的DOM不是原生的DOM,而是由React封装后的,包括事件(官方文档有讲解)。
  • 应用:在class组件的constructor中声明this.state,可以定义当前组件的内部状态,并且可以使用this.setState()方法来更新这个状态,每次state被更新时,上面提到的render会被调用用于生成一个新的
  • 在组件中使用外部插件:由于React基于js来构建,那些第三方插件,很容易被引用进来。

技术文档

由于技术文档内容挺多的,我只挑选一些我觉得重要的概念,并加以我的理解进行阐述。

核心概念

  • jsx:组件中的松散耦合。在一个文件中采用jsx的语法书写DOMjs,这里需要了解语法,并在使用的过程中实践。
  • props:让你可以从父组件传递消息到子组件内(包括事件/方法),消息通信让状态传递成为可能,而且使用的语法和写html属性差不多,挺友好的。
  • state:组件内部状态(Model)。通过调用setState来更新state,并触发render来得到新的视图(View)。
  • 事件处理:注意class组件需要bind事件的this。这就是我在本文开始提到的bonus,在js基础中,搞清楚this的指向挺重要的。另一个要点就是在React世界中,在DOM中直接触发的事件往往需要手动调用e.preventDefault()来阻止事件冒泡。
  • key:可以想象,在一组元素的对比和更新中,带有key会降低对比的工作量,加快我们更新的速度。React内部维护了一个Virtual DOM的概念,让列表更新更快。
  • 状态提升:在子组件中发送数据到父组件。学到这里,基本的父子组件通信就搞完了,原理就是调用props中得到的事件。
  • 哲学:切割页面,划分最小的组件对象,明确数据流向,明确事件分发方式。

高级指引

  • 语义化:这个往往被开发者忽视,实际上,读这个能了解别人解决问题的思考,适合看一看。
  • 代码分割:这个需要结合ES 6 Modulewebpack来看看,工程化的内容,待深入。
  • Refs:由于React是自己包的DOM元素,因此在父类要想拿到子类中的引用(通常这种情况我们需要从父类直接去调用子类的方法),就需要一个ref。被forwardRef之后实际是返回了一个高阶组件,在这里你能够拿到ref(被你从React.createRef()中创建的)。
  • 高阶组件:因为我们使用的React全是对象,那么想提升对象的能力,很容易想到的就是给对象包一层外壳,在java里面经常要分什么do\dto\bo\ao\vo等等,在React,利用js的优势(函数乃第一公民),可以很自然得到高阶组件这种概念。原理就是:一个纯函数,处理一个组件,返回另一个组件。
  • 性能优化:待深入
  • 其他:待深入

高级指引里面的内容,需要我们在实践中反复地回看,因此,这里就不赘述,每个人对这些有自己的理解吧。

开始一个项目

每个人都有自己的starter项目,我选择了minesweeper。这个游戏出现得挺早,但是我到高中上电脑课才从同学那知道它的玩儿法。我的想法是:规则挺简单,不需要我写很多游戏逻辑,不需要路由,可以拆分好几个组件,需要一点点状态管理和组件通信,正符合我这种初学React的人。

前期准备

前期我阅读了几篇文章,主要是这一篇,作者用react写了一个minesweeper

react-minesweeper
通过阅读别人的文章和代码,基本了解了一个作为项目而存在的React是什么个样子,接下来就只需要去实践它就行了。

接着我做了一些技术选型:

  1. React当然是必选了。并且我选择使用大家都广泛使用的create-react-app作为cli

  2. Redux&React-redux:如果是一个小游戏,本不需要状态管理。我认为项目的统一状态管理应该出现在状态多且复杂,而且需要众多组件同时响应时。但是从小了说,任意组件的状态可能都是有意义的(受到多个其他组件引用、可能成为全局状态)。因此还是选择使用react-redux,这样在我需要组件间通信的时候,我可以少写很多跳转,只需要专注于写一个组件。

    简单介绍一下,我是如何理解和使用react-redux的。

    1. 理解reducer。这个单词直译为减少,在Array.prototype.reduce中,它减少了空间量,将前后空间量作用于一个reducer函数,使得array(多个数据)变成一个单一的数据。但是在redux概念里,reducer需要放到生命周期里来看,一个应用的生命周期中,reducer会被调用无数次,因此我理解为它是把时间上的多个变量减少为单一变量,但是因为时间没有停止(时间停止,则应用也停止了,变量也不存在),因此每次我们只能拿到最终量的某一个时刻的样子,这就是reducer的产出被叫做nextState的原因。
    2. 唯一数据中心store。这个很好理解,一个项目只需要一个大脑就行了,保持数据源一致。
    3. 映射(map)。不管是mapStateToProps还是mapDispatchToProps,目的都是一个,把数据从store中取出来,挂载到UI组件上,以形成容器组件
    4. 容器(Container)。容器是一个高阶组件,它把自己获取到的StateDispatch转换为props挂载到UI组件上。使用时容器组件和被它包裹的UI组件是一个用法,区别是如果使用容器组件,我们可以从this.props中获取到store内的state,也能够分发dispatch
    5. conbineReducer。可以了解一下compose的概念和使用场景。
  3. rxjs:最开始是想使用rxjs的,但是在写得过程中发现很多事件都没必要用,简单地使用事件就可以解决了,在这个项目中,rxjs带来的提升并不明显,因此放弃了。

  4. TypeScript:有网友问我为什么没有用用TypeScript。因为,这个项目实在太小了,而我又需要快速完成它,如果我已经熟悉的语法和概念可以让我快速开发一个应用,那么为了一些ts带来的小优势而放弃了我另一个优势,那就得不偿失了。因此没有采用这个。

  5. redux-thunk:当我写到游戏状态更新、动画这一块的时候,我确实需要一些异步dispatch的能力了,这时候我选择引入redux-thunk。当然,知道这个库,也是google,从社区中了解到的。

完成技术选型,剩下的就是写起来。

写一个小项目

不管是小项目还是大项目,写的初衷有两个:

  1. 学习基本的React,为之后的工作做一些准备
  2. 反馈社区。自己从社区学习了很多优秀的思想,因此如果能够写一点点东西,帮助到和我一样的初学者,那再好不过了。
工程结构

我喜欢将一个项目各部分的职责划分得清楚一些

  • test:放置测试代码。单元测试,测试一些优秀的代码等等。
  • assets:放置静态资源。大多是图片。
  • components:放置组件。组件需要划分各自的职责,按模块/语义化名字来取。
  • constant:放置常量。字符串、数字等等。有点像java里面的enum
  • utilorhelper:辅助函数。和我的ui无关、state无关的内容,放置到这里。有些要是和业务强相关的话,可能会增加一个business目录。
拆分组件

要说minesweeper,首先想到的是一个面板,加一点游戏辅助信息。因此,我根据一个物件在页面出现的位置,划分了各个组件的职责。

  • Board:游戏面板。用于放置地图、辅助信息等。
    • 可能设计为横向布局:辅助信息|地图|描述信息
    • 也可能设计为纵向布局
    • 甚至你可以让辅助信息都在另一个页面,让游戏地图单独一个页面
  • Cell:方块。一个方块有多种状态,因此可以这么思考:
    • 翻开
      • 地雷
      • 数字
      • 空白
      • 多重图案
    • 未翻开
      • 空白
      • 加点色彩
  • Clock:时钟,这个可能会被全局用到,因此抽象为一个组件。
    • 表现形式可以多种多样
    • 对外提供时间状态到store
  • Description:用来写一些游戏描述信息
    • 游戏简介
    • 如何操作
  • Emoji:作为一个基于程序员和用户都喜欢的图标--emoji--来创建的项目,抽象出一个Emoji组件是很自然的事。(一个项目多个部分都会用到)
  • Info:展示游戏辅助信息,扩展性比较强
    • 可以设计分数
    • 展示游戏难度,甚至可以定制游戏方块
    • 游戏状态显示
    • 标记个数
    • 地雷总数
    • 时间显示
    • 如果加入对战,可以显示别人当前分数
    • 是否可以加入背景图像切换(背景不一样,难度不一样?)
    • 是否可以加入音乐(音乐紧凑程度可以反映了游戏难度)
  • Matrix:方块地图,整个游戏的互动区域
    • 如果是PC 这个Matrix就是游戏渲染屏幕,游戏地图
    • 但我也可以用来播放一些自定义的动画
    • 动画本身就是游戏的一部分
  • Row:为了更好地渲染Cell,在MatrixCell之间增加一层Row
重要部分
  1. 使用class语法:任何时候都要明确你的this指向在哪里。
  2. react-redux的灵活使用:在需要状态和不需要状态的情形下要做取舍;纯函数更新;异步更新;有什么好处。
  3. dispatch:分发鼠标事件到全局,用于更新store,并让各个部分有机刷新。
  4. 生命周期:这个在我的项目中用的少,但是单独提出来明确一下,大家使用一个技术时,一定要知道它各个部分会做些什么事。
  5. 拆分和组合:这个大家应该懂,整个程序员生涯都脱离不了这两个概念。
  6. 良好的分割
  7. 良好的UI

最后这两点(6/7)为啥很重要,我是这么思考的:

  1. 良好的业务和代码分割能够让新接手项目的更快上手(如果别人希望基于你的项目做定制,会有一个良好的体验)
  2. 多花一点时间去设计UI,可以让更多人喜欢上你的产品,因为大家都是从第一印象开始认识你的。

到这里,基本上我贡献了从开始阅读React文档到输出一个minesweeper游戏的所有思考。

  • 希望能够帮助到大家。
  • 希望能得到更多反馈,能够一起交流。

最后奉上项目地址,谢谢大家。

(以上文章仅来自作者(我)个人观点)