2019-08-01 10:14:44 lanzhsh 阅读数 17

banner

前言

前端生态越来越繁华,随着资本寒冬的来临,对前端招聘要求也变高了;
本文将从项目出发由浅入深做一个Vue,React,微信小程序,快应用,TS和 Koa的知识大串联;
相当于一篇文章搞定前端目前主流技术栈。

1.源码(持续更新)

话不多说,源码地址:Vue,React,微信小程序,快应用,TS 和 Koa 地址,欢迎 star
项目目录:
图片描述

2.vue 篇

2.1 vue-demo

2.1.1效果图

图片描述

Vue,React,微信小程序,快应用,TS 和 Koa 地址,欢迎 star

2.1.2.技术栈

vue+vue-router+vuex+axios+element-UI+iconfont(阿里)

2.1.3.适配方案

左侧固定宽度,右侧自适应
左侧导航和右侧导航分别配置滚动条

2.1.4.技能点分析

技能点 对应api
常用指令 @(v-on)绑定事件, v-if/v-show是否创建/和是否显示,v-for循环
生命周期 8个生命周期beforeCreate,created,beforeMount,mounted,beforeUpdate,updated,beforeDestroy和destroy
观察计算 computed和watch
data属性 定义变量,同样变量使用必须先定义
组件注册 components局部注册,Vue.component()全局注册
组件通讯 子传父:this.$emit,父传子:props,平级组件:vuex或路由传参
插件注册 Vue.use()注册插件,如Vue.use(element)是调用element内部的install方法
路由注册 vue-router:Vue.use(router)也是调用内部的install方法,挂载到vue实例中生成route和router属性
路由模式 mode属性可以设置history和hash
子路由 children:[]可以配置子路由
路由钩子 router.beforeEach(实现导航钩子守卫)和router.afterEach
vuex 4个属性,state,getters, actions(异步获取数据)和mutations(同步获取数据)
vuex 4个辅助函数,mapState,mapGetters, mapActions和mapMutations,就是辅助处理commit或distapch方法
axios 拦截器,interceptors.request请求拦截器,interceptors.response响应拦截器
axios baseUrl配置公共请求路径,必须符合http标准的链接,否则设置无效
axios 请求方法,get,post,put,delete等
axios 跨域,withCredentials: true,需要后端支持
css sass,对应嵌套不超过三层,滚动条样式设置,文本两行超出build问题
iconfont 阿里字体图标,可以自定义icon

2.1.5.那么问题来了?

computed和watch的区别? 解析
路由传参的方法? 解析
vue.use,vue.install,mixins方法区别? 解析
history和hash区别及实现原理? 区别解析原理解析vue-router官网
使用history和hash模式部署服务器有什么问题?问题解析
vuex的辅助函数和基本属性使用的区别?vuex官网
axios原理?axios源码
简单实现一个vue+vue-router+vuex的框架?

2.2 vue-mobile-demo

2.2.1 效果图

图片描述

2.2.2技术栈

vue+vue-router+vuex+vant+rem+sass+iconfont(阿里)
vant:有赞的电商mobile插件

2.2.3适配方案

rem

2.2.4技能点分析

iconfont的使用:官网配置icon,导出图标,引入assets目录下
vant使用:详见vant官网
全局配置rem:在index.html文件配置
全局配置sass函数和mixin:在build/utils下面的scss的options属性配置static目录下面的函数和混入

2.2.5那么问题来了

vue-cli生成的项目src下面的assets和根路径下面的static目录的区别?解析

3. react 篇

3.1 react-mobile篇

3.1.1效果图

图片描述
Vue,React,微信小程序,快应用,TS 和 Koa 地址,欢迎 star

3.1.2技术栈

react + react-router-v4 + redux +ant-design-mobile+iconfont
react-router-v4:路由4.x版本
redux:状态管理
ant-design-mobile:UI组件
iconfont:字体icon

3.1.3适配方案

rem适配

3.1.4技能点分析

技能点 对应的api
3种定义react组件方法 1.函数式定义的无状态组件; 2.es5原生方式React.createClass定义的组件; 3.es6形式的extends React.Component定义的组件
JSX react是基于jSX语法
react16之前生命周期 实例化(6个):constructor,getDefaultProps,getInitialState,componentWillMount,render,componentDidMount
react16生命周期 实例化(4个):constructor,getDerivedStateFromProps,componentWillMount,render,componentDidMount,componentWillUpdata,render,componentDidUpdate, componentWillUnmount
生命周期 更新(5个) componentWillReceivePorps,shouldComponentUpdate,
生命周期 销毁:componentWillUnmout
react-dom 提供render方法
react-router 4.x组成 react-router(核心路由和函数) , react-router-dom(API) , react-router-native( React Native 应用使用的API)
react-router 4.x的API router(只能有一个) , route(匹配路由渲染UI) , history, link(跳转) , navlink(特定的link,会带样式) , switch(匹配第一个路由) , redirect(重定向) , withRouter(组件,可传入history,location,match 三个对象)
react-router 3.x组成 就是react-router
react-router 3.x的API router , route , history(push和replace方法) , indexRedirect(默认加载) , indexRedirect(默认重定向) , link(跳转) , 路由钩子(onEnter进入,onLeave离开)4.x已经去掉
history react-router有三种模式:
1.browserHistory(需要后台支持);
2.hashHistory(有’#’);
3.createMemoryHistory
redux 单向数据流 , action(通过dispatch改变state值) , reducer(根据 action 更新 state) , store(联系action和reducer)
react-redux 1.连接react-router和redux,将组件分为两类:UI组件和容器组件(管理数据和逻辑) ,
2.connect由UI组件生成容器组件 ,
3.provider让容器组件拿到state ,
4.mapStateToProps:外部state对象和UI组件的props映射关系,
5.mapDispatchToProps:是connect第二个参数, UI 组件的参数到store.dispatch方法的映射
react-loadable 代码分割,相当于vue-router中的路由懒加载
classNames 动态css的类

3.2 react-pc-template篇

3.2.1效果图

图片描述
Vue,React,微信小程序,快应用,TS 和 Koa 地址,欢迎 star

3.2.2技术栈

dva+umi+ant-design-pro
dva:可拔插的react应用框架,基于react和redux
mui:集成react的router和redux
ant-design-pro:基于react和ant-pc的中后台解决方案

3.2.3适配方案

左侧固定宽度,右侧自适应
右侧导航分别配置滚动条.控制整个page

3.2.4技能点分析

技能点 对应api
路由 基于umi,里面有push,replace,go等方法
状态管理 dva里面的redux的封装,属性有state,effects,reducers
组件传值 父子:props,平级redux或umi的router
model 项目的model和dom是通过@connect()连接并将部分属性添加到props里
登陆 登陆是通过在入口js里面做路由判断

4.微信小程序篇

4.1小程序demo

4.1.1效果

图片描述

Vue,React,微信小程序,快应用,TS 和 Koa 地址,欢迎 star

4.1.2技术栈

weui+tabbar+分包+iconfont+自定义顶部导航+组件传值+wx.request封装
weui:Tencent推出的小程序UI

4.1.3适配方案

rpx:微信小程序的单位

4.1.4技能点分析

技能点 对应api
常用指令 bindtap绑定事件, wx:if/wx:show是否创建/和是否显示,wx:for循环
生命周期1 应用生命周期(app.js里):launch,show,hide
生命周期2 页面生命周期(page里):load,show,ready,hide,unload
生命周期3 组件周期(component里):created,attached,moved,detached
wx.request ajax请求
背景音乐 wx.getBackgroundAudioManager
音频 wx.createAudioContext
图片 wx.chooseImage
文件 wx.getFileInfo
路由 在app.json里面pages属性定义pages目录下面的文件
路由切换 wx.navigateTo,wx.navigateBack, wx.redirectTo,wx.switchTab,wx.reLaunch
分包 在app.json里面subPackages属性定义分包路由
weui组件 weui官网
原生组件 微信原生组件
业务组件 在json文件usingComponents注册
组件通讯 定义globalData,storage和路由

4.1.5那么问题来了

小程序的生命周期执行顺序?page和应用生命周期component生命周期解释
几种路由切换有什么不同?路由介绍
小程序怎么实现watch监听数据变化?实现watch

4.1.6小程序框架

wepy官网
基于wepy的商城项目
mpVue
基于mpVue的项目

分析:这两个框架都是通过预编译将对应风格的格式转化成小程序格式

5.快应用篇

5.1 快应用模板

5.1.1技能点分析

技能点 对应api
布局 基于弹性布局
指令 for:循环,if、show
生命周期 页面的生命周期:onInit、onReady、onShow、onHide、onDestroy、onBackPress、onMenuPress
app生命周期 onCreate、onDestroy
事件 on、off、emit、emitElement
路由配置 manifest文件的router属性配置
路由跳转 router.page
组件通讯 父子组件:$emit,props,兄弟组件:通过 Publish/Subscribe 模型
原生组件 list,map,tabs和canvas
消息机制 websocket使用

6.TS篇

6.1 TS前言

为什么会有TS?
大家有没想过这个问题,原因是JS是弱类型编程语言,也就是申明变量类型可以任意变换。所以这个时候TS出现了。
TS 是 JS 的超集,也相当于预处理器,本文通过一个template项目来让你快速上手TS。

6.2效果图

图片描述
Vue,React,微信小程序,快应用,TS 和 Koa 地址,欢迎 star

6.3技术栈

1.vue
2.vue-cli3
3.vue-router
4.vuex
5.typescript
6.iconfont

6.4核心插件

技能点 对应的api
vue-class-component 是vue官方提供的,暴露了vue和component实例
vue-property-decorator 是社区提供
深度依赖vue-class-component拓展出了很多操作符@Component @Prop @Emit @Watch @Inject
可以说是 vue class component 的一个超集,正常开发的时候 你只需要使用 vue property decorator 中提供的操作符即可

vue-property-decorator暴露的API

API 作用
@Component 注册组件
get 类似vue的computed
@Prop,@Emit 组件传值
@Watch 监听值变化
@Privde,@Inject 对应privde和inject
高阶组件用法,作用是多级父组件传值给子
@Model 类似vue的model

6.5 TS语法

数据类型 any(任意类型);
number;
string,
boolean;
数组:number[]或new Array(项的数据类型相同);
void返回值类型;
null;
undefined;
never(从不出现值);
元祖(比数组强大,项的类型可以不同);
接口:interface关键字;
对象:类似JS的object;
函数:function声明;
类:class关键字,包括字段,构造函数和方法
变量声明 let [变量名] : [类型] = 值, 必须指定类型
声明array,let arr: any[] = [1, 2]
运算符,条件语句,循环 同JS
函数 声明同JS,形参必须指定类型,因为形参也是变量
联合类型 通过竖线声明一组值为多种类型
命名空间 namespace关键字
模块 import和export
访问控制符 public,private(只能被其定义所在的类访问)和protected(可以被其自身以及其子类和父类访问)
默认public,是不是有点Java的味道

6.6问题来了

1.怎么在项目手动配置ts?
vue+ts项目配置

2.接口和类的区别?
接口只声明成员方法,不做实现 ,class通过implements 来实现接口
ts中接口和类的区别

3.接口和对象的区别?
接口是公共属性或方法的集合,可以通过类去实现;
对象只是键值对的实例

4.类class和函数的区别?
类是关键字class,函数是function
类可以实现接口

5.接口实现继承方法?

interface Person { 
   age:number 
} 
 
interface Musician extends Person { 
   instrument:string 
} 
 
var drummer = <Musician>{}; 
drummer.age = 27 
drummer.instrument = "Drums" 
console.log("年龄:  "+drummer.age)
console.log("喜欢的乐器:  "+drummer.instrument)

7. koa 篇

7.1 koa前言

node.js的出现前端已经可以用js一把梭,从前端写到后台。
本文从后台利用node的框架koa+mongodb实现数据的增删改查和注册接口,前端利用umi + dva +ant-design-pro来实现数据渲染。实现一个小全栈project,就是so-easy

7.2效果图

图片描述
Vue,React,微信小程序,快应用,TS 和 Koa 地址,欢迎 star

7.3技术栈

koa:node框架
koa-bodyparser:解析body的中间件
koa-router :解析router的中间件
mongoose :基于mongdodb的数据库框架,操作数据
nodemon:后台服务启动热更新

7.4项目目录

├── app // 主项目目录
│ ├── controllrts // 控制器目录(数据处理)
│ │ └── … // 各个表对应的控制器
│ ├── middleware // 中间件目录
│ │ └── resFormat.js // 格式化返回值
│ ├── models // 表目录(数据模型)
│ │ ├── course.js // 课程表
│ │ └── user.js // 用户表
│ └── utils // 工具库
│ │ ├── formatDate.js // 时间格式化
│ │ └── passport.js // 用户密码加密和验证工具
├── db-template // 数据库导出的 json 文件
├── routes // 路由目录
│ └── api // 接口目录
│ │ ├── course_router.js // 课程相关接口
│ │ └── user_router.js // 用户相关接口
├── app.js // 项目入口
└── config.js // 基础配置信息

7.5项目启动步骤

1.git clone
2.安装mongodb:http://www.runoob.com/mongodb/mongodb-window-install.html
3.安装 Robomongo 或Robo 3T是mongodb可视化操作工具 (可选)
4.启动
mongod (启动 mongodb)
打开Robomongo 或Robo
cd koa-template
npm i
npm run start
cd react-template
npm i
npm run start

注意:
mongodb启动默认端口号是27017,启动看是否被占用
后端项目端口号是3000,可以在koa-template/config.js里面修改

7.6 koa的主要API

API 作用
new koa() 得到koa实例
use koa的属性,添加中间件
context 将 node 的 request 和 response 对象封装到单个对象中,每个请求都将创建一个 Context,通过ctx访问暴露的方法
ctx方法 request:请求主体;
response:响应主体;
ctx.cookies.get:获取cookie;
ctx.throw:抛出异常
request属性 header:请求头;
method:方法;
url:请求url;
originalUrl请求原始URL;
href:完整URL;
hostname:主机名;
type:请求头类型;
response属性 header:响应头;
status:状态,未设置默认为200或204;
body:响应主体,string(提示信息) Buffer Stream(流) Object Array JSON-字符串化ull 无内容响应;
get:获取响应头字段;
set:设置响应头;
append:添加响应头;
type:响应类型;
lastModified:返回为 Date, 如果存在;
etag:设置缓存

7.7 koa-router主要API

API 作用
get get方法
post post方法
patch patch方法
delete delete方法
prefix 配置公共路由路径
use 将路由分层,同一个实例router中可以配置成不同模块
ctx.params 获取动态路由参数
fs 分割文件

7.8 mongoose主要API

API 作用
Schema 数据模式,表结构的定义;每个schema会映射到mongodb中的一个collection,它不具备操作数据库的能力
model schema生成的模型,可以对数据库的操作

model的操作database方法

API 方法
create/save 创建
remove 移除
delete 删除一个
deleteMany 删除多个
find 查找
findById 通过id查找
findOne 找到一个
count 匹配文档数量
update 更新
updateOne 更新一个
updateMany 更新多个
findOneAndUpdate 找到一个并更新
findByIdAndUpdate 通过id查找并更新
findOneAndRemove 找到一个并移除
replaceOne 替换一个
watch 监听变化

query查询API

API 作用
where 指定一个 path
equals 等于
or
nor 不是
gt 大于
lt 小于
size 大小
exists 存在
within 在什么之内

注:Query是通过Model.find()来实例化
aggregate(聚合)API

API 作用
append 追加
addFields 追加文件
limit 限制大小
sort 排序

注:aggregate=Model.aggregate()

更多详细API,请戳

2019-12-30 10:00:58 xutongbao 阅读数 85

目录

一、Promise

二、swiper

三、$event

四、transition 过渡动画

五、toast组件

六、解构并不能实现对象的深拷贝

七、修改脚手架默认包管理工具

八、redux

九、react组件props类型检查和默认值

十、使用webpack搭建react开发环境

github源码:


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

一、Promise

async函数会返回一个promise,并且Promise对象的状态值是resolved(成功的)

    //Promise基础用法
    let pro = new Promise(resolve => {
      setTimeout(() => {
        resolve('hello world')
      }, 500)
    })

    pro.then(res => {
      console.log(res) // hello world
    })

    //Promise和async await相结合,async函数会返回一个promise,并且Promise对象的状态值是resolved(成功的)
    const fun = async () => {
      let result
      await new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve({
            code: 200,
            data: { name: 'zhangsan' },
            message: '姓名'
          })
        }, 1000)
      }).then(res => {
        result = res
      })

      return result
    }

    ////Promise和async await相结合二
    fun().then(res => {
      console.log(res)
    })

    const fun2 = async () => {
      return await new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve({
            code: 200,
            data: 'lishi',
            message: '消息'
          })
        }, 1500)
      })
    }

    fun2().then(res => {
      console.log(res)
    })

 

二、swiper

swiper.js:

<template>
  <div>
    <div>
      <swiper v-if="bannerList.length > 0" :options="options">
        <swiper-slide v-for="(item,index) in bannerList" :key="index">
          <img :src="item" class="m-banner-img"/>
        </swiper-slide>
        <div class="swiper-pagination" slot="pagination"></div>
        <div class="swiper-button-prev" slot="button-prev"></div>
        <div class="swiper-button-next" slot="button-next"></div>
      </swiper>
    </div>
  </div>
</template>

<script>
import Api from "../api";

export default {
  data() {
    return {
      bannerList: [],
      options: {
        loop: true,
        effect: 'cube', //slide, fade,cube, coverflow, flip
        speed: 1000,
        autoplay: {
          delay: 2000
        },
        pagination: {
          el: '.swiper-pagination',
          clickable: true
        },
        navigation: {
          nextEl: '.swiper-button-next',
          prevEl: '.swiper-button-prev'
        }
      }
    };
  },
  mounted() {
    Api.getBanner().then(res => {
      if (res.code === 200) {
        this.bannerList = res.data;
      }
    });
  }
};
</script>

<style>
</style>

main.js:

import Vue from 'vue'
import lazyload from 'vue-lazyload'
import App from './App.vue'
import router from './router'
import store from './store'
import VueAwesomeSwiper from 'vue-awesome-swiper'
import 'swiper/dist/css/swiper.css'
import './font/iconfont.css'
import './index.css'
import img from './images/loading.png'


Vue.config.productionTip = false

Vue.use(lazyload, {
  loading: img
})

Vue.use(VueAwesomeSwiper)

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

三、$event

有时也需要在内联语句处理器中访问原始的 DOM 事件。可以用特殊变量 $event 把它传入方法。

https://cn.vuejs.org/v2/guide/events.html#%E5%86%85%E8%81%94%E5%A4%84%E7%90%86%E5%99%A8%E4%B8%AD%E7%9A%84%E6%96%B9%E6%B3%95

四、transition 过渡动画

<template>
  <div>
    <div>
      <router-link to="/index/home" class="m-nav-item">首页</router-link>
      <router-link to="/index/my_book" class="m-nav-item">书包</router-link>
      <router-link to="/index/exam1" class="m-nav-item">周考1</router-link>
    </div>
    <transition name="slide">
      <router-view class="m-router"></router-view>
    </transition>
  </div>
</template>

<script>
export default {
}
</script>

<style>
.m-router{position: absolute;width:100%}
.slide-enter-active{transition: all 1s linear;}
.slide-enter{transform: translateX(100%)}

/* .slide-leave-active{transition: all 1s linear;}
.slide-leave-to{transform: translateX(-100%)} */

.slide-leave-active{animation: slide 1s linear;}

@keyframes slide {
  0% {transform: translateX(0)}
  100% { transform: translateX(-100%)}
}
</style>

五、toast组件

Toast.vue:

<template>
  <div class="m-toast-mask" :id="id" >
    <div class="m-toast">{{message}}</div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      duration: 3000,
      message: '',
      id: Date.now()
    }
  },
  mounted() {
    setTimeout(() => {
      document.getElementById(this.id).remove()
    }, this.duration)
  }
}
</script>

<style>

</style>

index.js:

import Vue from 'vue'
import Toast from './Toast'

const ToastConstructor = Vue.extend(Toast)

const toast = (options) => {
  let instance = new ToastConstructor({data: options}).$mount()
  document.body.appendChild(instance.$el)
}

export default toast

css:

.m-toast-mask{display: flex; position: fixed;top: 0;left: 0;right: 0;bottom: 0;}
.m-toast{margin: auto;padding: 0 10px; min-width: 100px;line-height: 40px;border-radius: 3px;background: rgba(0, 0, 0, 0.5);text-align: center;color: #ffffff;}

使用:

<template>
  <div>
    <div v-for="(item, index) in myBook" :key="item.id" class="m-my-book-item">
      <div class="m-my-book-info">
        <label>
          <input type="checkbox" :checked="item.checked" @change="handleCheck(index, $event)" />
          {{item.title}}
        </label>
        ¥{{item.price}}
      </div>
      <div class="m-my-book-action">
        <button @click="handleSub(index)">-</button>
        {{item.count}}
        <button @click="handleAdd(index)">+</button>
        <button @click="handleDelete(index)">删除</button>
      </div>
    </div>
    <div v-if="myBook.length > 0">
      <div>
        <label>
          <input type="checkbox" :checked="total.checkedAll" @click="handleCheckAll">
          全选
        </label>
        <button @click="handleShowDialog">删除</button>
      </div>
      <div>总价:¥{{total.totalPrice}},总数:{{total.totalCount}}</div>
    </div>
    <div v-else>书包空空如也~~~</div>
    <Dialog :visible="visible" title="删除">
      <template v-slot:content>
        <div class="m-delete-info">
          <Icon type="shanchu" classname="m-delete-icon"></Icon>
          <div class="m-delete-text">
            你确定要删除选中的商品吗?
          </div>
        </div>
      </template>
      <template v-slot:footer>
        <button class="m-btn" @click="handleHideDialog">取消</button>
        <button class="m-btn" @click="handleDeleteChecked">确定</button>
      </template>
    </Dialog>
  </div>
</template>

<script>
import Api from "../api";
import Dialog from '../components/Dialog'
import Icon from '../components/Icon'
import toast from '../components/Toast'

export default {
  data() {
    return {
      visible: false
    }
  },
  computed: {
    myBook() {
      return this.$store.state.myBook;
    },
    total() {
      let myBook = this.myBook;
      let totalCount = myBook.filter(item => item.checked).reduce((total, item) => {
        return total + item.count;
      }, 0);
      let totalPrice = myBook.filter(item => item.checked).reduce((total, item) => {
        return total + item.count * item.price;
      }, 0);
      return {
        totalCount,
        totalPrice,
        checkedAll: myBook.every(item => item.checked)
      };
    }
  },
  components:{
    Dialog,
    Icon
  },
  methods: {
    handleSub(index) {
      let myBook = this.myBook;
      if (myBook[index].count > 1) {
        myBook[index].count--;
        this.$store.commit({ type: "setState", key: "myBook", value: myBook });
      }
    },
    handleAdd(index) {
      let myBook = this.myBook;
      myBook[index].count++;
      this.$store.commit({ type: "setState", key: "myBook", value: myBook });
    },
    handleDelete(index) {
      let myBook = this.myBook;
      myBook.splice(index, 1)
      this.$store.commit({ type: "setState", key: "myBook", value: myBook });
    },
    handleCheck(index, e) {
      let myBook = this.myBook;
      myBook[index].checked = e.target.checked
      this.$store.commit({ type: "setState", key: "myBook", value: myBook });
    },
    handleCheckAll(e) {
      let myBook = this.myBook;
      myBook.forEach(item => {
        item.checked = e.target.checked
      })
      this.$store.commit({ type: "setState", key: "myBook", value: myBook });
    },
    handleDeleteChecked() {
      let myBook = this.myBook;
      myBook = myBook.filter(item => !item.checked)
      this.$store.commit({ type: "setState", key: "myBook", value: myBook });
      this.handleHideDialog()
    },
    handleShowDialog() {
      if (this.myBook.filter(item => item.checked).length === 0) {
        //alert('请选择要删除的商品~')
        toast({message: '请选择要删除的商品~', duration: 1000})
        return
      }
      this.visible = true
    },
    handleHideDialog() {
      this.visible = false
    }
  },
  updated() {
    Api.update({ myBookNew: this.myBook }).then(res => {});
  },
  mounted() {
    this.$store.dispatch({ type: "getMyBook" });
  }
};
</script>

<style>
</style>

注册到全局:

import toast from './components/Toast'

Vue.prototype.$toast = toast

使用:

this.$toast({message: 'test'})

appendChild() 方法在指定元素节点的最后一个子节点之后添加节点。

https://www.w3school.com.cn/xmldom/met_element_appendchild.asp

 

六、解构并不能实现对象的深拷贝

      let obj = {
        a: 1,
        b: {
          c: '1'
        },
        fun() {
          console.log(1)
        },
        time: new Date(),
        d: undefined
      }
      let newObj = { ...obj }

      newObj.b.c = '2'
      console.log(obj)
      console.log(newObj)
      console.log(obj.b.c) // 2

七、修改脚手架默认包管理工具

八、redux

 

reducer 一定要保持纯净,只要传入参数相同,返回计算得到的下一个 state 就一定相同。没有特殊情况、没有副作用,没有 API 请求、没有变量修改,单纯执行计算。

时刻谨记永远不要在克隆 state 前修改它。

redux-devtools中,我们可以查看到redux下所有通过reducer更新state的记录,每一个记录都对应着内存中某一个具体的state,让用户可以追溯到每一次历史操作产生与执行时,当时的具体状态,这也是使用redux管理状态的重要优势之一.

若不创建副本,redux的所有操作都将指向内存中的同一个state,我们将无从获取每一次操作前后,state的具体状态与改变,若没有副本,redux-devtools列表里所有的state都将被最后一次操作的结果所取代.我们将无法追溯state变更的历史记录.

创建副本也是为了保证向下传入的this.props与nextProps能得到正确的值,以便我们能够利用前后props的改变情况以决定如何render组件。

直接修改state(错误的):

      const defaultState = {
        count: 0
      }

      //直接修改改state,所有的state都将被最后一次操作的结果所取代.我们将无法追溯state变更的历史记录
      const reducer = (state = defaultState ) => {
        state.count++
        return state
      }

      let previousState = { count: 0 }
      let newState = reducer(previousState)
      console.log(previousState)  //{ count: 1 }
      console.log(newState)       //{ count: 1 }

深拷贝(性能很差):

      const defaultState = {
        count: 0
      }

      //深拷贝可以解决这个问题
      const reducer = (state = defaultState ) => {
        state = JSON.parse(JSON.stringify(state))
        state.count++
        return state
      }

      let previousState = { count: 0 }
      let newState = reducer(previousState)
      console.log(previousState)  //{ count: 0 }
      console.log(newState)       //{ count: 1 }

解构:

      const defaultState = {
        count: 0
      }

      //解构可以解决这个问题
      const reducer = (state = defaultState ) => {
        let newState = {...state}
        newState.count++
        return newState
      }

      let previousState = { count: 0 }
      let newState = reducer(previousState)
      console.log(previousState)  //{ count: 0 }
      console.log(newState)       //{ count: 1 }

解构属于浅拷贝,解决不了嵌套的场景:

      const defaultState = {
        obj: {
          count: 0
        }
      }

      //解构属于浅拷贝,解决不了嵌套问题
      const reducer = (state = defaultState ) => {
        let newState = {...state}
        newState.obj.count++
        return newState
      }

      let previousState = {
        obj: {
          count: 0
        }
      }
      let newState = reducer(previousState)
      console.log(previousState)  //{ obj: { count: 1 } }
      console.log(newState)       //{ obj: { count: 1 } }

非要用解构,也行:

      const defaultState = {
        obj: {
          count: 0
        }
      }

      //解构属于浅拷贝,解决不了嵌套问题,不过可以这样解决,只是比较难理解
      const reducer = (state = defaultState ) => {
        let newObj = {...state.obj}
        newObj.count++
        return {...state, obj: newObj}
      }

      let previousState = {
        obj: {
          count: 0
        }
      }
      let newState = reducer(previousState)
      console.log(previousState)  //{ obj: { count: 0 } }
      console.log(newState)       //{ obj: { count: 1 } }

immutable.js:

<!DOCTYPE html>
<html>

<head>
</head>

<body>
  <div>
    <script src="https://cdn.bootcss.com/immutable/4.0.0-rc.12/immutable.js"></script>
    <script>

      const defaultState = Immutable.fromJS({
        obj: {
          count: 0
        }
      })

      //推荐使用immutable.js,由Facebook工程师耗时三年完成!
      const reducer = (state = defaultState ) => {
        return state.setIn(['obj', 'count'], Immutable.fromJS(state.getIn(['obj', 'count']) + 1))
      }

      let previousState = Immutable.fromJS({
        obj: {
          count: 0
        }
      })
      let newState = reducer(previousState)
      console.log(previousState.getIn(['obj']).toJS())  //{ count: 0 }
      console.log(newState.getIn(['obj']).toJS())       //{ count: 1 }

    </script>
  </div>


</body>

</html>

把业务代码提出来:

<!DOCTYPE html>
<html>

<head>
</head>

<body>
  <div>
    <script src="https://cdn.bootcss.com/immutable/4.0.0-rc.12/immutable.js"></script>
    <script>

      const defaultState = Immutable.fromJS({
        obj: {
          count: 0
        }
      })

      //推荐使用immutable.js,有Facebook工程师耗时三年完成!可以把业务代码提出来,reducer变成动态的key和动态的value
      const reducer = (state = defaultState, action ) => {
        return state.setIn(action.key, Immutable.fromJS(action.value))
      }

      let previousState = Immutable.fromJS({
        obj: {
          count: 0
        }
      })
      let action = { key: ['obj'], value: { count : 1} } 
      let newState = reducer(previousState, action)
      console.log(previousState.getIn(['obj']).toJS())  //{ count: 0 }
      console.log(newState.getIn(['obj']).toJS())       //{ count: 1 }

    </script>
  </div>


</body>

</html>

九、react组件props类型检查和默认值

不需要安装prop-types包,脚手架已经默认安装

import React, { Component } from 'react'
import PropTypes from 'prop-types'

export default class Icon extends Component {
  // static defaultProps = {
  //   type: 'xingxing'
  // }
  render() {
    let { type, className } = this.props
    return (
      <span className={`icon iconfont icon-${type} ${className ? className : ''}`} onClick={this.props.onClick}></span>
    )
  }
}

Icon.propTypes = {
  type: PropTypes.string,
  className: PropTypes.string
}

Icon.defaultProps = {
  type: 'shubao'
}

参考链接:

https://zh-hans.reactjs.org/docs/typechecking-with-proptypes.html#___gatsby

https://www.npmjs.com/package/prop-types

 

十、使用webpack搭建react开发环境

webpack.config.js:

const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const HtmeWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  mode: 'development',
  devtool: 'source-map',
  entry: __dirname + '/index.js',
  output: {
    path: __dirname + '/dist',
    filename: 'bundle-[hash].js'
  },
  devServer: {
    inline: true,
    hot: true,
    port: 9000,
    contentBase: __dirname + '/dist',
    open: true
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ["@babel/preset-react", "@babel/preset-env"]
          }
        },
        exclude: /node_modules/
      }, {
        test: /\.css$/,
        use: [{
          loader: MiniCssExtractPlugin.loader
        }, {
          loader: 'css-loader'
        }]
      }]
  },
  plugins: [
    new HtmeWebpackPlugin({
      template: __dirname + '/public/index.html'
    }),
    new MiniCssExtractPlugin({
      filename: '[name]-[hash].css'
    })
  ]
}

package.json:

{
  "name": "toast",
  "version": "1.0.0",
  "description": "",
  "main": "/toast/index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack",
    "start": "webpack-dev-server"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.7.7",
    "@babel/preset-env": "^7.7.7",
    "@babel/preset-react": "^7.7.4",
    "babel-loader": "^8.0.6",
    "css-loader": "^3.4.2",
    "html-webpack-plugin": "^3.2.0",
    "mini-css-extract-plugin": "^0.9.0",
    "react": "^16.12.0",
    "react-dom": "^16.12.0",
    "style-loader": "^1.1.2",
    "webpack": "^4.41.5",
    "webpack-cli": "^3.3.10",
    "webpack-dev-server": "^3.10.1"
  }
}

github:

https://github.com/baweireact/m-apps/tree/master/m-app-1708E/react/01%E4%BD%BF%E7%94%A8webpack%E6%90%AD%E5%BB%BAreact%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83

 

十一、webpack搭建react项目时在二级路由页面刷新报404的解决办法

react-router 使用webpack-dev-server做服务器时 单级刷新报错 Cannot GET /xx。多级刷新报错404

 

  output: {
    path: __dirname + '/dist',
    filename: 'bundle-[hash].js',
    publicPath: '/'   //第二处要添加的
  },
  devServer: {
    inline: true,
    hot: true,
    port: 9000,
    contentBase: __dirname + '/dist',
    open: true,
    historyApiFallback: true  //第一处要添加的
  },

参考链接:

https://blog.csdn.net/limonsea/article/details/96865906

十二、webpack图解

webpack+webpack-dev-server:

https://segmentfault.com/q/1010000005767033/a-1020000005767079

 

通过node启动webpack-dev-server:

https://webpack.docschina.org/guides/hot-module-replacement/#%E9%80%9A%E8%BF%87-node-js-api

 

devServer.overlay:

这个配置属性用来在编译出错的时候,在浏览器页面上显示错误,默认是false,可设置为true

devServer: {
    overlay: true
}

十三、import对象解构失败问题

用惯了es6语法中的解构赋值,在对于import引入的对象进行解构赋值时发现对象变成了undefined

解决办法:

// main.js
import { foo, bar } from "./static"

// static.js
let foo =  "foo"
let bar = "bar"
export { foo, bar }

参考链接:

https://blog.csdn.net/momDIY/article/details/88375677

 

 

 

 

 

 

 

 

 

 

 

 

 

 

github源码:

https://github.com/baweireact/m-apps/tree/master/m-app-1708E

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

2019-12-30 07:52:58 xutongbao 阅读数 826

目录

 

 

 

 

一、git

1、git简介

2、github简介

3、在github上创建项目

4、克隆代码

5、提交代码

6、git工作区

7、分支

8、对比分支

9、生成ssh key

10、安装git

11、安装小乌龟

12、忽略提交.gitignore

13、git配置用户名和邮箱

14、git免密

参考链接

二、MySql

1、安装mysql

2、安装Navicat

3、破解Navicat

4、Navicat连接mysql报错的解决办法

5、使mysql允许外部连接访问

6、Navicat建立MySql连接

7、sql语句简介

8、nodejs封装sql查询

9、不使用vue.config.js,单独使用node

10、封装api接口

11、axios拦截器

12、redis

13、jwt-simple

14、uuid

15、node操纵mysql数据库进行增删查改以及登录退出

三、vue

1、周考一

四、react

1、hook入门

五、微信小程序

1、书城

 

github源码


 

 

 

 

 

 

 

一、git

1、git简介

Git 是一个开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目。

Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。

Git 与常用的版本控制工具 CVS, Subversion 等不同,它采用了分布式版本库的方式,不必服务器端软件支持。

2、github简介

章鱼猫:GitHub的吉祥物(Octocat)

GitHub是一个面向开源及私有软件项目的托管平台,因为只支持git 作为唯一的版本库格式进行托管,故名GitHub。

2018年6月4日,微软宣布,通过75亿美元的股票交易收购代码托管平台GitHub。

 

github搜索star最多的项目: stars:>10000

---

vue和react在npm上的下载量对比:

https://npmcharts.com/compare/react,vue?interval=7

 

 

3、在github上创建项目

4、克隆代码

git clone git@github.com:baweireact/m-app-test.git

5、提交代码

添加要上传的文件:

git add README.md

提交到本地:

git commit -m "first commit"

提交到远程:

git push origin master

  

git add readme.txt

git add readme.txt ant.txt

git add *.html

git add all 

git add .

git add *

git log

git status

git add . 会把本地所有untrack的文件都加入暂存区,并且会根据.gitignore做过滤,但是git add * 会忽略.gitignore把任何文件都加入 

6、git工作区

使用 git add 命令将想要快照的内容写入缓存区, 而执行 git commit 将缓存区内容添加到本地仓库中。 

 

7、分支

::切换到develop分支
git checkout beta

git pull origin beta

::把develop分支的代码合并到beta上
git merge develop

git status

::push到远程beta
git push origin beta

::切换到develop分支 
git checkout develop

echo. & pause 

新建分支:

git branch feature_x

将分支提交到远程:

git push origin feature_x

切换分支:

git checkout master

查看有所有分支:

git branch -a

查看本地分支:

git branch

创建新分支并立即切换到该分支下:

git checkout -b feature_login

 

在github上查看有哪些分支:

删除本地分支:

git branch -d feature_x

删除远程分支:

git push origin -d feature_x

合并分支,合并后再提交到远程仓库(先切换到master分支,然后合并feature_login分支到master分支,然后再把合并后的代码提交到远程仓库):

git merge feature_login

git push origin master

查看历史记录(按Q键退出历史记录):

git log

查看简洁历史记录:

git log --oneline

反顺序的历史记录:

git log --reverse

 

8、对比分支

git diff master feature_login

 

9、生成ssh key

  git clone 时,可以所用不同的协议,包括 ssh, git, https 等,其中最常用的是 ssh,因为速度较快,还可以配置公钥免输入密码

ssh-keygen -t rsa -C "1183391880@qq.com"

 

 

生成的key的位置:

github添加key:

点击setting:

 

新建ssh key :

 

把生成的key粘贴到github上:

 

 

 确保启用 SSH 代理:

$ eval "$(ssh-agent -s)"

为 SSH key 启用 SSH 代理:

$ ssh-add ~/.ssh/id_rsa

10、安装git

11、安装小乌龟

配置ssh:

 

12、忽略提交.gitignore

在使用Git的过程中,我们喜欢有的文件比如日志,临时文件,编译的中间文件等不要提交到代码仓库,这时就要设置相应的忽略规则,来忽略这些文件的提交。

/mtk 过滤整个文件夹
*.zip 过滤所有.zip文件

参考链接:

https://www.jianshu.com/p/74bd0ceb6182

 

13、git配置用户名和邮箱

   全局配置用户名:

git config --global user.name "xutongbao"

   全局配置邮箱:

git config --global user.email "1183391880@qq.com"

14、git免密

ssh配置失败的同学,可以用https协议下载代码,而且也可以配置免密!

用git时,每次都需要输入密码会比较麻烦。
可以进行设置,这样在输入过一次密码之后,以后就不需要每次都输入密码了。
打开终端输入 :

touch ~/.git-credentials

再输入:

git config --global credential.helper store

 

 

 

 

 

 

 

 

 

 

 

 

 

参考链接

git使用简易指南:

https://www.bootcss.com/p/git-guide/

加入到暂存区参考链接:

http://www.softwhy.com/article-8489-1.html

设置ssh key出问题的可以参考下面的链接:

https://blog.csdn.net/felicity294250051/article/details/53606158

https://blog.csdn.net/goawaycow1/article/details/78069487

 

二、MySql

1、安装mysql

2、安装Navicat

3、破解Navicat

 

4、Navicat连接mysql报错的解决办法

 

第一句:修改加密规则

第二句:更新用户密码

第三句:刷新权限

alter user 'root'@'localhost' identified by 'root' password expire never;

alter user 'root'@'localhost' identified with mysql_native_password by 'root';

flush privileges;

参考链接:

https://www.jianshu.com/p/c8eb6d2471f8

5、使mysql允许外部连接访问

update user set host='%' where user='root';

flush privileges;

6、Navicat建立MySql连接

7、sql语句简介

 通过sql语句实现增删查改:

-- 建表
create table user (
	id varchar(100) primary key,
	username varchar(100),
	password varchar(100)
);

-- 增
insert into user values ('001', 'admin', '123456');
insert into user values ('002', 'xu', '123456');
insert into user values ('003', 'xutongbao', '123456');

-- 删
delete from user;

delete from user where id = '003';

-- 查
select * from user;

select username from user;

select * from user where id = '002';

-- 模糊搜索
select * from user where username like '%n%';

-- 降序
select * from user order by username desc;

-- 升序
select * from user order by username asc;

-- 分页
select * from user order by username asc limit 1,2;

-- 数量
select count(*) from user;


-- 改
update user set password = '123';

update user set password = '1234' where id = '001';

8、nodejs封装sql查询

mysqlQuery.js:

const mysql = require('mysql')

let connection = mysql.createConnection({
  host: 'localhost',
  user: 'root',
  password: 'root',
  database: 'demo'
})

connection.connect((error) => {
  if (error) {
    console.log('失败')
  } else {
    console.log('成功')
  }
})

//使用回调函数返回查询结果
const query = (sql, callback) => {
  connection.query(sql, (error, results) => {
    if (error) {
      throw error
    } else {
      callback && callback(results)
    }
  })
}

//使用promise封装sql查询
const queryPromise = (sql) => {
  return new Promise((resolve, reject) => {
    connection.query(sql, (error, results) => {
      if (error) {
        reject(error)
      } else {
        resolve(results)
      }
    })
  })
}

module.exports = {
  query,
  queryPromise
}

vue.config.js:

const { query, queryPromise } = require('./mysqlQuery')

module.exports = {
  devServer: {
    open: true,
    before(app) {
      app.get('/api/get_user_query', async (req, res) => {
        query(`select * from user`, (results) => {
          res.send({
            code: 200,
            data: results,
            message: '用户列表'
          })
        })
      })
      app.get('/api/get_user', async (req, res) => {
        let results = await queryPromise(`select * from user`)
        res.send({
          code: 200,
          data: results,
          message: '用户列表'
        })
      })
    }
  }
}

9、不使用vue.config.js,单独使用node

主要是为了可以使用nodemon,后端代码改变时不需要重启

const express = require('express')
const cors = require('cors')
const { queryPromise } = require('./mysqlQuery')

const app = express()

app.use(cors())

app.get('/api/get_user', async (req, res) => {
  let users = await queryPromise('select * from user')
  res.send({
    code: 200,
    data: users,
    message: '用户列表'
  })
})

app.listen(3000, () => {
  console.log('3000端口')
})

参考链接:

跨域:

http://www.expressjs.com.cn/en/resources/middleware/cors.html

express:

http://www.expressjs.com.cn/starter/hello-world.html

nodemon:

https://www.npmjs.com/package/nodemon

 

10、封装api接口

index.js:

import axios from 'axios'
import url from './url'

axios.defaults.baseURL = 'http://localhost:3000'

const common = async (config) => {
  const response = await axios(config)
  return response
}

export default {
  getUser: () => common({ url: url.getUser })
}

url.js:

export default {
  getUser: '/api/get_user'
}

11、axios拦截器

前端请求前添加token,请求后判断状态码:

import axios from 'axios'
import url from './url'

axios.defaults.baseURL = 'http://localhost:3000'

axios.interceptors.request.use((config) => {
  config.headers.token = '666'
  return config
})

axios.interceptors.response.use((res) => {
  if (res.data.code === 200) {
    return res
  } else if (res.data.code === 400) {
    alert(res.data.message)
    return res
  } else if (res.data.code === 403) {
    window.location.href = '/login'
  }
})

const common = async (config) => {
  const response = await axios(config)
  return response
}

export default {
  getUser: () => common({ url: url.getUser })
}

后端接收token:

const express = require('express')
const cors = require('cors')
const { queryPromise } = require('./mysqlQuery')

const app = express()

app.use(cors())

app.get('/api/get_user', async (req, res) => {
  let token = req.headers.token
  console.log(token)
  let users = await queryPromise('select * from user')
  res.send({
    code: 400,
    data: users,
    message: '用户列表'
  })
})

app.listen(3000, () => {
  console.log('3000端口')
})

 

参考链接:

http://www.axios-js.com/docs/#Interceptors

12、redis

用于保存token

安装:

启动:

在控制台使用:

//启动
redis-server.exe redis.windows.conf

//访问
redis-cli.exe -h 127.0.0.1 -p 6379

//设置值
set a 1

//获取值
get a

//删除key
del a

//设置一个值20秒后删除
set a 1 EX 20

参考链接:

https://www.npmjs.com/package/redis

13、jwt-simple

用于生成token

参考链接:

https://www.npmjs.com/package/jwt-simple

14、uuid

用于生成用户id

参考链接:

https://www.npmjs.com/package/uuid

15、node操纵mysql数据库进行增删查改以及登录退出

app.js:

const express = require('express')
const cors = require('cors')
const redis = require('redis')
const bodyParser = require('body-parser')
const jwt = require('jwt-simple')
const uuidv1 = require('uuid/v1')
const { queryPromise } = require('./mysqlQuery')

const app = express()

//跨域
app.use(cors())
app.use(bodyParser.json())

var secret = 'xxx';

//如果没有启动redis,会报错,启动redis方法,在cd到redis的安装目录,执行redis-server.exe redis.windows.conf
const client = redis.createClient()
client.on('error', (err) => {
  console.log('redis错误:' + err)
})

//检查token是否存在,并更新token过期时间
const checkToken = async (token) => {
  let result = await new Promise((resolve) => {
    client.get(token, function (err, res) {
      return resolve(res);
    });
  });
  console.log(result)
  if (result) {
    client.set(token, token , 'EX', 600)
    return true
  } else {
    return false
  }
}

//增
app.post('/api/add_user', async (req, res) => {
  let token = req.headers.token
  let { username, password } = req.body
  let isLogin = await checkToken(token)
  if (isLogin) {
    let users = await queryPromise(`select * from user where username = '${username}'`)
    console.log(users)
    if (users.length > 0) {
      res.send({
        code: 400,
        data: {
          username
        },
        message: '用户名已存在'
      })
    } else {
      let id = uuidv1()
      let user = await queryPromise(`insert into user values('${id}', '${username}', '${password}')`)
      if (user) {
        res.send({
          code: 200,
          data: user,
          message: '添加成功'
        })
      }
    }
  } else {
    res.send({
      code: 403,
      message: '登录过期'
    })
  }
})

//删
app.post('/api/delete_user', async (req, res) => {
  let token = req.headers.token
  let { ids } = req.body
  let isLogin = await checkToken(token)
  if (isLogin) {
    let idString = ''
    for (let i = 0; i < ids.length; i++) {
      if (i === ids.length - 1) {
        idString += ` id = '${ids[i]}' `
      } else {
        idString += ` id = '${ids[i]}' or `
      }
    }
    let result = await queryPromise(`delete from user where ${idString}`)
    res.send({
      code: 200,
      data: result,
      message: '删除成功'
    })
  } else {
    res.send({
      code: 403,
      message: '登录过期'
    })
  }
})

//查
app.get('/api/get_user', async (req, res) => {
  let token = req.headers.token
  let isLogin = await checkToken(token)
  if (isLogin) {
    let users = await queryPromise('select * from user')
    res.send({
      code: 200,
      data: users,
      message: '用户列表'
    })
  } else {
    res.send({
      code: 403,
      message: '登录过期'
    })
  }
})

//改
app.post('/api/update_user', async (req, res) => {
  let token = req.headers.token
  let { id, username, password } = req.body
  let isLogin = await checkToken(token)
  if (isLogin) {
    let result = await queryPromise(`update user set username = '${username}', password = '${password}' where id = '${id}' `)
    res.send({
      code: 200,
      data: result,
      message: '更新成功'
    })
  } else {
    res.send({
      code: 403,
      message: '登录过期'
    })
  }
})

//登录
app.post('/api/login', async (req, res) => {
  let { username, password } = req.body
  let users = await queryPromise('select * from user')
  let user = users.find(item => item.username === username && item.password === password)
  if (user) {
    let token = jwt.encode(user.id, secret)
    client.set(token, token , 'EX', 600)  //60秒后验证码过期知道
    res.send({
      code: 200,
      data: {
        username,
        token
      },
      message: '登录成功'
    })
  } else {
    res.send({
      code: 400,
      message: '登录失败'
    })
  }
})

//退出
app.post('/api/login_out', (req, res) => {
  let token = req.headers.token
  client.del(token)
  res.send({
    code: 200,
    message: '退出'
  })
})

app.listen(3000, () => {
  console.log('3000端口')
})

mysqlQuery.js:

const mysql = require('mysql')

let connection = mysql.createConnection({
  host: 'localhost',
  user: 'root',
  password: 'root',
  database: 'demo'
})

connection.connect((error) => {
  if (error) {
    console.log('失败')
  } else {
    console.log('成功')
  }
})

//使用回调函数返回查询结果
const query = (sql, callback) => {
  connection.query(sql, (error, results) => {
    if (error) {
      throw error
    } else {
      callback && callback(results)
    }
  })
}

//使用promise封装sql查询
const queryPromise = (sql) => {
  return new Promise((resolve, reject) => {
    connection.query(sql, (error, results) => {
      if (error) {
        reject(error)
      } else {
        resolve(results)
      }
    })
  })
}

module.exports = {
  query,
  queryPromise
}

 

三、vue

1、周考一

 

四、react

1、hook入门

React的组件化给前端开发带来了前所未有的体验,我们可以像玩乐高玩具一样将组件堆积拼接起来,组成完整的UI界面,在加快开发速度的同时又提高了代码的可维护性。

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

hook = 钩子

 

参考链接:

https://cloud.tencent.com/developer/article/1468196

https://react.docschina.org/docs/hooks-intro.html

useState、useEffect:

import React, { useState, useEffect } from 'react'

const App = () => {
  const [ count, setCount ] = useState(0)
  const [ obj, setObj ] = useState({
    name: 'xu',
    job: 'web'
  })

  //初始化state可以使用函数
  const [ name, setName ] = useState(() => {
    return 'xu'
  })
  
  //每次更新完都会执行
  useEffect(() => {
    console.log(count)
    return () => {
      console.log('执行当前effect之前对上一个effect进行清除!' + count)
    }
  })

  //只执行一次
  useEffect(() => {
    console.log('只执行一次!')
  }, [])

  useEffect(() => {
    console.log('count更新时执行' + count)
  }, ['count'])

  return (
    <div>
      { count }
      <div>
        <button onClick={() => { setCount(count - 1) }}>减</button>
        <button onClick={() => { setCount(count + 1) }}>加</button>
        <button onClick={() => { setCount(prevCount =>  {
          return prevCount + 1
        }) }}>加</button>
        <button onClick={() => setCount(0)}>重置</button>
      </div>
      { obj.name }, { obj.job }
      <div>
        <button onClick={() => setObj({ name: '徐' })}>改名(删除了job字段)</button>
        <button onClick={() => setObj( prevObj => {
          return {...prevObj,  name: '星河'}
        })}>改名(不会删除job字段)</button>
      </div>
      <div>
        {name}
      </div>
    </div>
  )
}

export default App

useReducer:

import React, { useReducer } from 'react'

const initalState = { count: 0 }

const reducer = (state, action) => {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 }
    case 'decrement':
      return { count: state.count - 1 }
    default:
      return state
  }
}

const App = () => {
  const [state, dispatch] = useReducer(reducer, initalState)
  return (
    <div>
      {state.count}
      <div>
        <button onClick={() => { dispatch({ type: 'decrement' }) }}>减</button>
        <button onClick={() => { dispatch({ type: 'increment' }) }}>加</button>
      </div>
    </div>
  )
}

export default App

使用函数初始化state:

import React, { useReducer } from 'react'

const initalState = { count: 0 }

const init = (a) => {
  return {count: a.count + 1}
}

const reducer = (state, action) => {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 }
    case 'decrement':
      return { count: state.count - 1 }
    default:
      return state
  }
}

const App = () => {
  const [state, dispatch] = useReducer(reducer, initalState, init)
  return (
    <div>
      {state.count}
      <div>
        <button onClick={() => { dispatch({ type: 'decrement' }) }}>减</button>
        <button onClick={() => { dispatch({ type: 'increment' }) }}>加</button>
      </div>
    </div>
  )
}

export default App

useRef,输入框获取焦点:

import React, { useRef, useEffect } from 'react'

const App = () => {
  const inputEl = useRef(null)

  const handleFocus = () => {
    inputEl.current.focus()
  }  

  useEffect(() => {
    inputEl.current.focus()
  }, [])

  return (
    <div>
      <input ref={inputEl}></input>
      <div>
        <button onClick={handleFocus}>获取焦点</button>
      </div>
    </div>
  )
}

export default App

自定义hook,获取上一轮的state:

import React, { useState, useRef, useEffect } from 'react'

//自定义hook,获取上一轮的state
const usePrevious = (value) => {
  const ref = useRef()
  useEffect(() => {
    ref.current = value
  })
  return ref.current
}

const App = () => {
  const [ count, setCount ] = useState(0)

  const prevCount = usePrevious(count)

  return (
    <div>
      {count}, {prevCount}
      <div>
        <button onClick={() => setCount(count + 1)}>加</button>
      </div>
    </div>
  )
}

export default App

获取数据:

function SearchResults() {
  const [data, setData] = useState({ hits: [] });
  const [query, setQuery] = useState('react');

  useEffect(() => {
    let ignore = false;

    async function fetchData() {
      const result = await axios('https://hn.algolia.com/api/v1/search?query=' + query);
      if (!ignore) setData(result.data);
    }

    fetchData();
    return () => { ignore = true; }
  }, [query]);

  return (
    <>
      <input value={query} onChange={e => setQuery(e.target.value)} />
      <ul>
        {data.hits.map(item => (
          <li key={item.objectID}>
            <a href={item.url}>{item.title}</a>
          </li>
        ))}
      </ul>
    </>
  );
}

2、路由懒加载,代码分割

参考链接:

https://zh-hans.reactjs.org/docs/react-api.html#reactsuspense

https://blog.csdn.net/xutongbao/article/details/84822315

import React, { Component, Suspense, lazy } from 'react'
import { Switch, Route, NavLink, Redirect } from 'react-router-dom'
// import Home from './Home'
// import MyBook from './MyBook'
import Detail from './Detail'
import Loading from '../components/Loading'
const MyBook = lazy(() => import('./MyBook'))
const Home = lazy(() => import('./Home'))

class Index extends Component {
  render() {
    return (
      <div>
        <div>
          <NavLink to="/index/home" className="m-nav-item">首页</NavLink>
          <NavLink to="/index/my_book" className="m-nav-item">书架</NavLink>
        </div>
        <Suspense fallback={<Loading></Loading>}>
          <Switch>
            <Route exact path="/index/home" component={Home}></Route>
            <Route path="/index/my_book" render={() => {
              if (localStorage.getItem('username')) {
                return <MyBook></MyBook>
              } else {
                return <Redirect to="/login"></Redirect>
              }
            }}></Route>
            <Route path="/index/home/detail/:id" component={Detail}></Route>
          </Switch>
        </Suspense>
      </div>
    )
  }
}

export default Index

3、图片懒加载

装包:

yarn add react-lazy-load
        <LazyLoad height={300} onContentVisible={() => console.log(item.title)}>
          <img src={item.avatar} ></img>
        </LazyLoad>

 参考链接:

https://www.npmjs.com/package/react-lazy-load

 

五、微信小程序

1、购物车

 

2、入门

申请账号:

https://mp.weixin.qq.com/wxopen/waregister?action=step1

安装开发者工具:

登录账号:

https://mp.weixin.qq.com/

3、轮播图

参考链接:https://www.cnblogs.com/zjjDaily/p/8041734.html

  <swiper 
    indicator-dots="{{true}}"
    autoplay="{{true}}"
    interval="{{1000}}"
    style="height:{{height}}px">
    <swiper-item wx:for="{{banner}}" wx:key="{{index}}">
      <image src="{{item}}" mode="widthFix" class="m-item-img" bindload="handleImageLoad"></image>
    </swiper-item>
  </swiper>

4、tabbar

  "tabBar": {
    "list": [{
      "text": "首页",
      "selectedIconPath": "./static/index-active.png",
      "iconPath": "./static/index.png",
      "pagePath": "pages/home/index"
    }, {
      "text": "书架",
      "selectedIconPath": "./static/cart-active.png",
      "iconPath": "./static/cart.png",
      "pagePath": "pages/mybook/mybook"
    }]
  }

 5、不支持状态管理

https://developers.weixin.qq.com/community/develop/doc/d4b0566fcd760a529181f2d4b009341c?_at=1558693595388

六、node 

1.koa,egg

$ npm i egg-init -g
$ egg-init egg-example --type=simple
$ cd egg-example
$ npm i

egg官网:https://eggjs.org/zh-cn/intro/quickstart.html

 

 

 

 

 

 

github源码

https://github.com/baweireact/m-app-1705E

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

2019-04-21 22:43:16 sinat_17775997 阅读数 42

公司使用微信小程序做了不少东西,发现的痛点越来越多。没有组件化,配置繁锁,生命周期名字不一。基于它才有了vue系列的解决方案,我在想,能不能搞一套React的解决方案呢。毕竟以React为技术栈的公司不在少数,销路肯定很好。

我首先从定义组件着手。微信小程序是存在自定义组件的机制,但不能使用继承。并且一个组件也拆得好碎,分成四块。JS定义,CSS,模板与配置。

JS定义

微信小程序是没有组件机制,只是提供了一个工厂方法Component给你定义组件。如果模糊来看,Page也相当于组件,因为它也有data与setData方法。这相当于React的state与setState。App相当于React中的无状态组件,它可以定义一个所有页面都能访问的globalData对象,还有一些事件。我们发现App,Page,Component的一些生命周期是与路由器挂钩的,不像React,React只针对自身。

我们用React的概念来类似小程序吧,这样对没有接触过小程序的同学比较好理解

 

Component({
  behaviors: [], //相当于mixin,不建议使用
  properties: { //相当于propTypes
    myProperty: 1111
    myProperty2: 222 
  },
  data: {}, //  相当于state
  created(){},   //componentWillMount或是getDerivedStateFromProps
  attached(){}, //componentDidMount
  moved(){},   //componentDidUpdate可以模拟它
  detached(){}, //componentWillUnmount
  methods: {//放事件回调及其他方法,已经bind this, 现在也可以不用特意放在这个对象上,这其实是早期抄袭vue的东西
  }
})

 

小程序没有shouldComponentUpdate, 我们可以对setData的第一个参数做一些手脚,如果为false,返回一个空对象。

this.setData( (function(self){

 

   //调用原来的shouldComponentUpdate逻辑
      if(shouldComponentUpdate(self.properties, self.data ) ){   
         return newData;
      } else{
         return {};
      }
})(this),function(){
   //调用原来的componentDidUpdate逻辑
   setStateCb && setStateCb.call(this)
   componentDidUpdate && componentDidUpdate.call(this)
})

 

如此一来,React的生命周期就能一一对上号了,当然如果大家想一套代码共用touch与weixin,建议不要用shouldComponentUpdate,这个模拟成本很高,可能与React的效果差距很大。

CSS

CSS,在小程序中叫WXSS,是一个弱化版的CSS。文档中也介绍不要使用id,说明它无法做到scoped。但如果纯是写CSS没什么意义,我们公司已经大量使用less, sass等预处理语言,因此未来也会向这个方向发展。

模板

小程序将组件的模板独立出来,使用经典的JSP风格嵌入变量,还添加了wx:if, wx:for这些指令实现常规的if, for操作。因此许多人就将它与vue类似起来。但它与vue来比,还是很弱,首先,它没有双向绑定(美曰其名为单向流动),其次事件绑定时只能使用方法名,不能动态生成函数,也不能指定一个函数,再次也混杂了一些奇怪的标签,template, block, slot。template是应该是web component的东西,block是后端模块的东西, slot是vue的。

 

<view class="wrapper">
  <view>这里是组件的内部节点</view>
  <slot></slot>
</view>


相当于React这样的代码

 

<div class="wrapper">
 <div>这里是组件的内部节点</div>
  {this.props.children}
</div>

 

相对于React, vue,它没有ref这种指令,它是不想让我们得到元素节点,但我们可以定义一些data-*属性,然后在组件的attached钩子中通过this.dataset拿到它们。但相当于React, 我们是拿不到真正的props。在小程序中, properties与data是同一个东西

Component({

 

   data:{a: 1},
   attached: function(){
      console.log(this.data === this.properties) //true
   }
})

jsx中的this.xxx, this.props.xxx要统一进行去“this.”操作,这个用 babel 处理没什么难度。

配置

App,Page, Component都有相应的json对象,主要是定义弹窗的颜色,页面的颜色及一些子组件的引用。这些可以抽取成组件的静态属性,这样代码就更加内聚。

在我动手之前,业界其实已经有相关的方案出来,比如 weact, taro了。我所组织的技术群,也有一帮同好在做这东西,看来潮流不可逆转,小程序这种反人类的粗糙滥造之物必须再封装才方便我们迅速推进业务。就像sass, less于之css, typescript于之es5。

原文https://yq.aliyun.com/articles/613887

2019-06-19 18:08:34 tzllxya 阅读数 242

React转微信小程序:双模板联动

 

这是本系列的最后一篇。小程序封死了操作DOM的可能性,并且也不让我们操作视图,所有与视图有关的东西一律接触不了。而它的自定义组件是非常恶心,基本不配叫组件,不能继承叫什么组件。因此我们使用它更早期的动态模板技术,template。

我的思路如下,通过编译组件的render方法,将里面的自定义组件变成template类,然后在template类中自己初始化,得到props, state再传给小程序的template标签。换言之,有两套模板。

 

//源码
import { Page } from "../wechat";
import "./page.css";
import Dog from "../components/dog/dog";
const e = "e";
class P extends Page {
  constructor(props) {
    super(props);
    this.state = {
      name: 'hehe',
      array: [
        {name: "dog1",text: "text1"},
        {name: "dog2",text: "text2"},
        {name: "dog3",text: "text3"},
      ]
    };
  }

  onClick() {
    console.log("test click1" + e);
  }
  render() {
    return (
      <div>
        <div>
          {this.state.array.map(function(el) {
            return <Dog name={el.name}>{el.text}</Dog>;
          })}
        </div>
        <Dog name={this.state.name} />
      </div>
    );
  }
}
export default P;

我们先不管Dog组件长得怎么样。

为了让它同时支持小程序与React的render函数,我们需要对render进行改造。将Dog,div等改造成小程序能能认识的类型,如

 <view>
    <view>
      {this.state.array.map(function(el) {
        return <template is={Dog} name={el.name}>{el.text}</template>;
      })}
    </view>
    <template is="Dog" name={this.state.name} />
  </view>

这个转译是如何实现呢,我们可以通一个插件 syntax-jsx, 它会在visitor遍历出JSX的开标签,闭标签,属性及{}容器。

 

但React无法认识template标签,因此还要改造

//React专用
 <view>
    <view>
      {this.state.array.map(function(el) {
        return <React.template is={Dog} name={el.name}>{el.text}</React.template>;
      })}
    </view>
    <React.template is={Dog} name={this.state.name} />
  </view>

现在看小程序这边

小程序无法认识{},需要改变成wx:for指令

//小程序专用
 <view>
    <view>
      <block wx:for="{{this.state.array}}" wx:for-item="el">
         <template is="Dog" name={el.name}>{el.text}</template>;
      </block>
    </view>
    <template is="Dog" name={this.state.name} />
  </view>

小程序的template有个缺憾,它无法认识name这样的属性的,因此我们需要一个东西装着它。那么我们动态创建一个数组吧,改一改React那边:

//React专用
 <view>
    <view>
      {this.state.array.map(function(el) {
        return <React.template is={Dog} name={el.name} templatedata="data123124342">{el.text}</React.template>;
      })}
    </view>
    <React.template is={Dog} name={this.state.name} templatedata="data34343433" />
  </view>

templatedata这个属性及它的值是babel在编译时创建的,React.template到时会在this.data.state添加data123124342数组,内容为一个个对象,这些对象是通过Dog.props, Dog.defaultProps, Dog.state组成。结构大概是{ props: {}, state: {} }

那么小程序的模板则改成这样,去掉各种template不认识的东西,加上wx:for属性,它对应React方的templatedata值。然后template有一个data属性,通过对象解构,完美把所有属性(除方法)传到dog的模板中。

//小程序专用
<import src="../../components/dog/dog.wxml" />
 <view>
     <view>
      <block wx:for="{{this.state.array}}" wx:for-item="el">
         <template is="Dog" wx:for="data123124342" wx:for-item="data" data="{{...data}}"></template>;
      </block>
    </view>
    <template is="Dog" wx:for="data34343433" wx:for-item="data" data="{{...data}}" />
  </view>

然后我们再把React的render方法改成React.createElement形式:

import { Page } from "../wechat";
import "./page.css";
import Dog from "../components/dog/dog";
const e = "e";
class P extends Page {
  constructor(props) {
    super(props);
    this.state = {
      name: 'hehe',
      array: [
        {name: "dog1",text: "text1"},
        {name: "dog2",text: "text2"},
        {name: "dog3",text: "text3"},
      ]
    };
  }

  onClick() {
    console.log("test click1" + e);
  }
  render() {
    return (
      React.createElement(
          "div",
          null,
          React.createElement(
            "div",
            null,
            this.state.array.map(function(el) {
              return React.createElement(React.template, {
                name: el.name,
                children: el.text,
                is: Dog,
                templatedata:"data34343433"
              });
            })
          ),
          React.createElement(React.template, {
            is: Dog,
            name: this.state.name,
            templatedata:"data34343433"
          })
        );
}
export default P;

上面的转译工作可以通过transform-react-jsx babel插件实现

class P extends Page这种es6定义类的方式,小程序可能也不认识,或者通过babel编译后也太复杂。比如说taro将Dog这个类变成这样:

 

有长有臭,因此我们最好在React中提供一个定义类的方法,叫miniCreateClass。如此一来我们就能将Dog转换得很简洁

var React = require("../../wechat");
var Component = React.Component
var miniCreatClass = React.miniCreatClass

function Dog() {}

let Dog = miniCreatClass(Dog, Component, {
  render: function () {
    return React.createElement("view", null, this.state.name )
  }
}, {});
module.exports.default = Dog;

我们再看Page类。小程序定义页面是通过 Page 工厂实现的,大概是Page({data: {}})。小程序在这里的令计很方便我们进行hack,因为一个Page类只会有一个实例。

Page(createPage(P))

createPage的实现大既这样:

function createPage(PageClass) {
  var instance = ReactDOM.render(React.createElement(PageClass), {
    type: "div",
    root: true
  });
  var config = {
    data: {
      state: instance.state,
      props: instance.props
    },
    onLoad: function() {
      instance.$wxPage = this;
    },
    onUnload: function() {
      instance.componentWillUnmount && instance.componentWillUnmount();
    }
  };
  instance.allTemplateData.forEach(function(el) {
    if (config.data[el.templatedata]) {
      config.data[el.templatedata].push(el);
    }else{
      config.data[el.templatedata] = [el];
    }
  });
  return config;
}

最后是React.template的实现,它负责组装给template的数据,这个template是小程序的标签。

React.template = function(props){
//这是一个无状态组件,负责劫持用户传导下来的类,修改它的原型
   var clazz = props.is;
   var a = classzz.prototype;
   var componentWillMount = a.componentWillMount;
   a.componentWillMount = function(){
    
     var ref = this._reactInternalRef;
     var arr = ref._owner.allTemplateData || (ref._owner.allTemplateData = []);
     arr.push({
       props: this.props,
       state: this.state,
       templatedata: props.templatedata
     })
     componentWillMount && componentWillMount.call(this)
   }
  var componentWillUpdate = a.componentWillUpdate;
   //...再上面一样
   return React.createElement(clazz, props)
}

react与微信小程序

阅读数 104

没有更多推荐了,返回首页