这周的工作重头戏,非渠道活动页莫属了!要用 React + webpack 写的一个页面,还要与后端对接,这两个都是我之前完全没有接触过的东西!最期待的就是前后端联调。
参考的代码和 webpack 的配置都是用的师兄的,代码风格跟他一样,只是根据我的业务代码对代码做出了调整。这让我对代码的借鉴和创新的看法又冒出了脑海,就是用别人的代码到底好还是不好?
创新与借鉴
实际上,我的理性在跟我说,借鉴代码是合情合理的,就像一个木匠要雕刻出一个东西,在入门阶段,他一定要看着大师的作品来雕,等到理解出精髓,或者足够熟练后,才有能力总结出一套属于自己的东西,实现自己的灵感。
但是当我使用别人的代码的时候,总有种我在抄袭的“愧疚感”,思考原因,可能是在我小时候那些年,中国总在讲创新创新,似乎创新才是正义,当你视图照搬别人的东西,你就会被鄙视,首当其冲的大概就是腾讯了,那时候的我也觉得腾讯可恶,别人有什么抄什么,让我对模仿起了抵触的情绪。其实现在来看,靠抄袭起家的腾讯,如今家大业大,也诞生了属于自己的产品,比如微信,而又有多少企业能完全不靠借鉴同行业的东西起家呢,诚然有,那些人必是划时代的,是写进历史的人,诚然不是每个人都能从零制造出一个产品。落到实处上讲,假如让你用编程语言写出一个计算器,不许借鉴其他人的思路和实现方法,让你写一个出来,这样,无异于让你从头发明一个计算器出来!如果你真的从对计算器的实现原理一无所知到最后写出一个计算器,那毫无疑问,你是优秀的,但是毫无疑问,你会花上更多更多的时间。如果你去 github 上看别人计算器的实现,了解到计算器算法的实现,再用自己的方法实现一次,你的需求实现了,可以完成任务,你也学到了东西,就是计算器的算法,就像ZW师兄说的,举一反三,那你就有了属于自己的东西!就能在有了做计算器的能力的基础上实现更多的东西!因为你已经有了积累。所以啊,还是要习惯这种从借鉴甚至照搬的学习方式,不断吸收别人的知识,收为己用,最重要的是要从借鉴,照搬中学到东西。如果只是实现业务而不去思考实现原理,那也只是会用而不会运用。同时也要在保证完成任务的前提下拿起来就用,不要太囿于原理无从下手,所以稍后会分析下师兄代码的精妙之处,加以整理。
代码梳理与思考
一、’Webpack
首先看的是Webpack的配置,第一个模块就是‘webpack-blocks’——又有新东西啦。
看了一下,webpack-blocks 相当于 webpack 配置写法的语法糖,把一些已经繁琐的 webpack 配置简写。
example:
// 以前在 webpack 中定义常量
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
})
// 以前区分开发与生产环境时
let judgeJsCssPath = () => {
// 声明常量后使用三元运算符做判断
return isProd ? prodJsCSSPath : watchJsCSSPath;
}
// 以前使用 CSS-loader 和 postcss
module: {
rules: [
{
test: /\.css$/,
use: [
{ loader: "style-loader" },
{ loader: "css-loader" },
{ loader: "postcss-loader", }
]
},
]
}
// 使用 webpack-blocks
const {
setEnv,
env,
createConfig,
css,
postcss
} = require('webpack-blocks');
module.exports = createConfig([
setEnv({
NODE_ENV: process.env.NODE_ENV
}),
env('development', [
// 开发模式下的配置
]),
env('production', [
// 生产模式下的配置
]),
css(),
postcss([
// postcss的配置
])
])
可以看到使用了 webpack-blocks 后不用再写 module, pulugins 这两部分,只要引入 createConfig,就可以在createConfig中插入
loader 和 plugins。
这只是一份十分简略的 weback-bloacks 配置,具体可看 :
andywer/webpack-blocks: 📦 Configure webpack using functional feature blocks.
继续分析配置
配置中 开发服务器的IP 设置不是写死的,作者写了一个函数用于判断IP地址,引入了 Node 中的 os 模块,调用 os.networkInterfaces()
,作用是返回一个计算机中被赋予网络地址的网络接口组成的对象。通过这个方式拿到计算机中可以使用的那个IP地址。
配置被分成了两份,第一份定义好使用的ip地址,文件存放,打包地址,出入口,第二份才是要使用到的插件等工具的配置。
发现环境有三个:"development"、"production"、"pre-build",看 package.json 发现,pre-build 用于 build:watch 阶段,即文件已打包,在 dist 文件夹下看一个服务器,这时候监听的是 dist 文件夹,意味着网页可以在 IE8 下打开,修改 js、css 时也会被服务器监听到,实现在 IE8 下调样式、debug。那么build:watch下修改了源文件后,是否需要再 build 一下呢,这个还不知道。从 production 和 pre-build 的配置上看,两个阶段的配置是不同的,所以应该还是需要调试完再打包一次。
简单的分析,但是对配置中一些配置项比如 resolve 还是一知半解,名词恐惧症 ,现在还不想去了解。
二、业务代码分析
这次使用的是 React + typescript + Webpack ,要想实现一个星期掌握使用一个框架,最主要的就是要有足够的资料,除了要有文档,还有有一份成品demo,有了实践样本,才能真的利用一个框架写出业务。
typings
使用 typescript 的时候用到了 typeings ,typings 可以用于自动代码提示和代码规范,比如检查函数传参类型是否正确,参数个数是否缺少,如果报错将会无法通过编译。typings 是靠人编写的,要编写 typings 还是挺繁琐的。
师兄的代码风格最明显的就是 UI 和 逻辑分离,UI 就只写逻辑,UI放在Store的里,因为只有一个活动页,所以不用 Redux,因为需要管理的状态不多,为了写一个活动页引入 Redux 文件太大,有点大材小用,采用的方法是声明一个对象 store,把状态保存在 store 中,组件要产生什么变化先查询 store 中的状态,根据状态渲染出不同的样式。
这种思想是之前困扰我的:页面中一个组件发生变化,怎么让其他组件也发生联动变化,方法就是——利用一个中间人,当组件A发生变化,通知中间人,把变化告诉中间人,中间人把这个状态保存起来,其他组件保持对中间人的监听,当中间人保存的状态发生变化,其他组件收到变化的信号,也跟着发生变化,这就是思想啊,get 到了就很愉悦。
在我做的这个页面中,需要保存的状态就是用户名,登录态,活动id,每个活动的状态,签到功能的签到天数等。Store文件夹中有个 index.ts 和一个 util.ts,两个都有逻辑,index.ts 的逻辑主要是跟修改 store中的状态有关,util.ts的主要是跟一些要用到的工具函数,如登录,埋点上报,页面中需要用到的“复制到剪切板”等函数。组件要使用只要引入 util ,用解构 引入需要的函数即可。
css 的使用
css 也是使用模块化的写法,组件的目录结构是这样的:
├── App.tsx
├── Head
├── ├── Head.scss
├── ├── Head.tsx
├── ├── index.tsx
├── Lottery
├── ├── index.tsx
├── ├── Lottery.scss
├── ├── Lottery.tsx
├── Nav
├── ├── index.tsx
├── ├── Nav.scss
├── ├── Nav.tsx
├── NewUser
├── ├── index.tsx
├── ├── NewUser.scss
├── ├── NewUser.tsx
├── Rules
├── ├── index.tsx
├── ├── Rules.scss
├── ├── Rules.tsx
├── SignIn
├── ├── index.tsx
├── ├── SignIn.scss
├── ├── SignIn.tsx
├── User
├── ├── index.tsx
├── ├── User.scss
├── ├── User.tsx
可以看到每个组件一个index, scss ,组件文件,index 只复杂 import 再 export 组件文件。scss文件在组件文件中以如下方式引入:
import * as styles from 'xx.scss';
组件每使用一个样式都要用styles.container
的形式引入,这样每个样式都被模块化,即使其他组件也用container命名class也不会有冲突,如果有一些样式要用 js 的方式引入,这时候无法判断最后生成的 class 名是怎么样的(加了hash),可以把样式放在 :global
中,:global中的样式不会被模块化。
可配置的页面文字
在页面中,有一些文字可能在开发时还不知道具体上线时正式的文案是什么,比如游戏规则,礼包名称,需要运营的同事后期去修改,实际中不可能每次运营要修改什么都通知前端,让前端去修改,这样也可以,但是很繁琐,可能影响前端日常开发效率,于是,这部分文字就被分离,放在一个 js 文件中,文件中有个 js对象,文字都是对象里每个属性的值,再把这个配置文件引入
html :
<script src = "xxx/xxx/config.js"></script>
这样这个对象就被引入 window 中,定义一个变量 let winConfig = window["_config"]
这样文字都通过config文件引入,于是上线后运营同事只要修改 config 中的文字对应的网页上的文字也会发生变化,同时,该文件必须放入无缓存文件夹,即该文件每次开发网页都重新请求,避免用户的网页没有刷新。
这又是体现了“分离”的思想。低耦合,可配置。你的变动,不会受到我影响,我的变动也不会影响你,但是你知道我变动了。
在使用了配置文件后在 IE7 下还踩了一个坑,就是文件最好遵照 json 的格式,最后一个属性的最后不要加逗号:
// bad
let config = {
"setting": {
"rule": [
"xxxxx",
"xxxxx", // 后面已经没有属性,但留了个逗号
]
}
}
// good
let config = {
"setting": {
"rule": [
"xxxxx",
"xxxxx" // 不要添加逗号
]
}
}
如果加了逗号, IE7 下会保错。
说到 IE7 还踩到了一个奇怪的坑,就是文件已经打包好了后在 IE 8 下运行,发现 es5-shim 报错,提示"没有权限“,看了一手师兄的 debug ,在 es5-shim 的 github 源码上找到报错的那一句,判断是什么原因出了问题,直接看源码,他觉得那个函数是没有问题的,在他的 IE 下,包括后来我在虚拟机上的测试都没有问题,就电脑上的那个 IE 有坑 ...
Debug 的姿势
说到看师兄 debug,最印象深刻的就是,他会排除到所以可能会影响的因素,即排除变量法,只保留出问题的那个函数,看看是否出现问题,再根据出现的情况判断出现的问题在哪。这是很值得借鉴的一种 debug 方式。
开发中发现的一件事就是,很多以为要写的逻辑,要么是 SDK 帮你做了,就是后端接口可以帮你做了,抽奖转盘怎么写,调
sdk,怎么让用户登录,调 sdk,怎么判断用户签到了几天,调 sdk,怎么创建提示窗口,调 sdk ...... ,怎么储存用户每次的获奖记录,用后端接口,怎么写分页,后端根据设置返回记录条数......
然后我们要做的就是操纵各种 sdk(工具),来与后端对接,调用 sdk 的方法,sdk 调用后端接口,只要传入相应的游戏 id 和任务 id,后端返回状态码,状态码 200,返回奖品名,把奖品名字段储存在前端,再把字段放在网页中输出。再根据其他非 200 的情况弹出不同的弹窗。
应该只是接了一个需求简单的页面需求,只需要借助 sdk 和 后端接口,看其他的网页中的一些奇怪的需求,后端 sdk没考虑到的逻辑还是要前端去实现出来。实现也分简单的实现和优雅的实现,有的人把 UI 和逻辑都写在组件中,我也把一些判断的东西写入 ui 看能不能想方法把逻辑的东西抽出来,还有写了一个有5个参数的函数,这也并不优雅。
动态列表后面跟一个复制按钮
这个也是困扰我很久的问题!就是每次都读到问了 ZW 师兄,我以为他也会没想到,或者解释很长,但是人家三下五除二就写出了实现的方法,就是利用 html 中的 自定义属性作为 js 中的 hook,因为我们每次都是点击复制按钮,一列的 <li> 有一列的 复制按钮,怎么判断点的是哪个按钮,事件中有个属性叫 target !指向的就是那个被点击的DOM 元素!,虽然要保存的文字保存在另一个 DOM 元素上,但是,凭什么只保存在那个DOM元素上,我是开发者!我也可以把文字放在 复制 按钮上,但是要不被人看见,那就放在元素的属性上!自定义一个 “data-con”,把文字存放在 “data-con” 上,每次点击读取 data-con的值即可!
// '.bagClip' 即复制按钮
$(document).on('click', '.bagClip', (e) => {
util.clipboard({
txt: $(e.target).data('con')
})
$('.bagClip').removeClass('cliped');
$(e.target)[0].className += ' cliped'
})
这次写业务实现了两个思维突破,这就是借鉴别人代码的好处啊!没必要自己发明出点击按钮实现复制文本的功能!
(e.target: 指向绑定了事件的那个 DOM 元素;e.currentTarget:真正被触发到(被戳到)的那个元素)
网友评论