項目實戰【vue,react,微信小程序】(1708E)

目錄

一、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

十四、FileZilla連接ftp服務器失敗,提示"AUTH TLS"解決方法

https://www.cnblogs.com/mytt/p/6684199.html

 

 

 

 

 

 

 

 

 

 

 

 

github源碼:

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

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章