日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > HTML >内容正文

HTML

送给前端开发者的一份新年礼物

發布時間:2025/3/17 HTML 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 送给前端开发者的一份新年礼物 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

大家好,新年快樂!今天,我開源了一個 React 的項目。這個項目雖小,但是五臟六腑俱全。

先來介紹下這個項目的技術棧:

  • React 全家桶:React 16 + Redux + React-router 4.0 + Immutable.js
  • ES6 + ES7 語法
  • 網絡請求:Axios + Socket.io
  • UI 框架:Antd-mobile
  • 后端:Express + MongoDB

React 是什么

React 其實只是一個 UI 框架,頻繁進行 DOM 操作的代價是很昂貴的,所以 React 使用了虛擬 DOM 的技術,每當狀態發生改變,就會生成新的虛擬 DOM 并與原本的進行改變,讓變化的地方去渲染。并且為了性能的考慮,只對狀態進行淺比較(這是一個很大的優化點)。

React 已經成為當今最流行的框架之一,但是他的學習成本并不低并且需要你有一個良好的 JS 基礎。由于React 只是一個 UI 框架,所以你想完成一個項目,你就得使用他的全家桶,更加提高了一個學習成本。所以本課程也是針對初學者,讓初學者能夠快速的上手 React 。

React 組件

如何寫好規劃好一個組件決定了你的 React 玩的溜不溜。一個組件你需要考慮他提供幾個對外暴露的接口,內部狀態通過局部狀態改變還是全局狀態改變好。并且你的組件應該是利于復用和維護的。

組件的生命周期

  • render 函數會在 UI 渲染時調用,你多次渲染就會多次調用,所以控制一個組件的重復渲染對于性能優化很重要
  • componentDidMount 函數只會在組件渲染以后調用一次,通常會在這個發起數據請求
  • shouldComponentUpdate 是一個很重要的函數,他的返回值決定了是否需要生成一個新的虛擬 DOM 去和之前的比較。通常遇到的性能問題你可以在這里得到很好的解決
  • componentWillMount 函數會在組件即將銷毀時調用,項目中在清除聊天未讀消息中用到了這個函數
父子組件參數傳遞

在項目中我使用的方式是單個模塊頂層父組件通過 connect 與 Redux 通信。子組件通過參數傳遞的方式獲取需要的參數,對于參數類型我們應該規則好,便于后期 debug。

性能上考慮,我們在參數傳遞的過程中盡量只傳遞必須的參數。

路由

在 React-router 4.0 版本,官方也選擇了組件的方式去書寫路由。

下面介紹一下項目中使用到的按需加載路由高階組件

import React, { Component } from "react"; // 其實高階組件就是一個組件通過參數傳遞的方式生成新的組件 export default function asyncComponent(importComponent) {class AsyncComponent extends Component {constructor(props) {super(props);// 存儲組件this.state = {component: null};}async componentDidMount() {// 引入組件是需要下載文件的,所以是個異步操作const { default: component } = await importComponent();this.setState({component: component});}// 渲染時候判斷文件下完沒有,下完了就渲染出來render() {const C = this.state.component;return C ? <C {...this.props} /> : null;}}return AsyncComponent; }復制代碼

Redux

Redux 通常是個另新手困惑的點。首先,不是每個項目都需要使用 Redux,組件間通信不多,邏輯不復雜,你也就不需要使用這個庫,畢竟這個使用這個庫的開發成本很大。

Redux 是與 React 解耦的,所以你想和 Redux 通信就需要使用 React-redux,你在 action 中使用異步請求就得使用 Redux-thunk,因為 action 只支持同步操作。

Redux 的組成

Redux 由三部分組成:action,store,reducer。

Action 顧名思義,就是你發起一個操作,具體使用如下:

export function getOrderSuccess(data) { // 返回的就是一個 action,除了第一個參數一般這樣寫,其余的參數名隨意return { type: GET_ORDER_SUCCESS, payload: data }; } 復制代碼

Action 發出去以后,會丟給 Reducer。Reducer 是一個純函數(不依賴于且不改變它作用域之外的變量狀態的函數),他接收一個之前的 state 和 action 參數,然后返回一個新的 state 給 store。

export default function(state = initialState, action) {switch (action.type) {case GET_ALL_ORDERS:return state.set("allOrders", action.payload);default:break;}return state; } 復制代碼

Store 很容易和 state 混淆。你可以把 Store 看成一個容器,state 存儲在這個容器中。Store 提供一些 API 讓你可以對 state 進行訪問,改變等等。

PS:state 只允許在 reducer 中進行改變。

說明完了這些基本概念,我覺得是時候對 Redux 進行一點深入的挖掘。

自己實現 Redux

之前說過 Store 是個容器,那么可以寫下如下代碼

class Store {constructor() {}// 以下兩個都是 store 的常用 APIdispatch() {}subscribe() {} } 復制代碼

Store 容納了 state,并且能隨時訪問 state 的值,那么可以寫下如下代碼

class Store {constructor(initState) {// _ 代表私有,當然不是真的私有,便于教學就這樣寫了this._state = initState}getState() {return this._state}// 以下兩個都是 store 的常用 APIdispatch() {}subscribe() {} } 復制代碼

接下來我們考慮 dispatch 邏輯。首先 dispatch 應該接收一個 action 參數,并且發送給 reducer 更新 state。然后如果用戶 subscribe 了 state,我們還應該調用函數,那么可以寫下如下代碼

dispatch(action) {this._state = this.reducer(this.state, action)this.subscribers.forEach(fn => fn(this.getState())) } 復制代碼

reducer 邏輯很簡單,在 constructor 時將 reducer 保存起來即可,那么可以寫下如下代碼

constructor(initState, reducer) {this._state = initStatethis._reducer = reducer } 復制代碼

現在一個 Redux 的簡易半成品已經完成了,我們可以來執行下以下代碼

const initState = {value: 0} function reducer(state = initState, action) {switch (action.type) {case 'increase':return {...state, value: state.value + 1}case 'decrease': {return {...state, value: state.value - 1}}}return state } const store = new Store(initState, reducer) store.dispatch({type: 'increase'}) console.log(store.getState()); // -> 1 store.dispatch({type: 'increase'}) console.log(store.getState()); // -> 2 復制代碼

最后一步讓我們來完成 subscribe 函數, subscribe 函數調用如下

store.subscribe(() =>console.log(store.getState()) ) 復制代碼

所以 subscribe 函數應該接收一個函數參數,將該函數參數 push 進數組中,并且調用該函數

subscribe(fn) {this.subscribers = [...this.subscribers, fn];fn(this.value); } constructor(initState, reducer) {this._state = initStatethis._reducer = reducerthis.subscribers = [] } 復制代碼

自此,一個簡單的 Redux 的內部邏輯就完成了,大家可以運行下代碼試試。

Redux 中間件的實現我會在課程中講解,這里就先放下。通過這段分析,我相信大家應該不會對 Redux 還是很迷惑了。

Immutable.js

我在該項目中使用了該庫,具體使用大家可以看項目,這里講一下這個庫到底解決了什么問題。

首先 JS 的對象都是引用關系,當然你可以深拷貝一個對象,但是這個操作對于復雜數據結構來說是相當損耗性能的。

Immutable 就是解決這個問題而產生的。這個庫的數據類型都是不可變的,當你想改變其中的數據時,他會clone 該節點以及它的父節點,所以操作起來是相當高效的。

這個庫帶來的好處是相當大的: - 防止了異步安全問題 - 高性能,并且對于做 React 渲染優化提供了很大幫助 - 強大的語法糖 - 時空穿梭 (就是撤銷恢復)

當然缺點也是有點: - 項目傾入性太大 (不推薦老項目使用) - 有學習成本 - 經常忘了重新賦值。。。

對于 Immutable.js 的使用也會在視頻中講述

性能優化

  • 減少不必要的渲染次數
  • 使用良好的數據結構
  • 數據緩存,使用 Reselect

具體該如何實現性能優化,在課程的后期也會講述

聊天相關

在聊天功能中我用了 Socket.io 這個庫。該庫會在支持的瀏覽器上使用 Websocket,不支持的會降級使用別的協議。

Websocket 底下使用了 TCP 協議,在生產環境中,對于 TCP 的長鏈接理論上只需要保證服務端收到消息并且回復一個 ACK 就行。

在該項目的聊天數據庫結構設計上,我將每個聊天存儲為一個 Document,這樣后續只需要給這個 Document 的 messages 字段 push 消息就行。

const chatSchema = new Schema({messageId: String,// 聊天雙方bothSide: [{user: {type: Schema.Types.ObjectId},name: {type: String},lastId: {type: String}}],messages: [{// 發送方from: {type: Schema.Types.ObjectId,ref: "user"},// 接收方to: {type: Schema.Types.ObjectId,ref: "user"},// 發送的消息message: String,// 發送日期date: { type: Date, default: Date.now }}] }); // 聊天具體后端邏輯 module.exports = function() {io.on("connection", function(client) {// 將用戶存儲一起client.on("user", user => {clients[user] = client.id;client.user = user;});// 斷開連接清除用戶信息client.on("disconnect", () => {if (client.user) {delete clients[client.user];}});// 發送聊天對象昵稱client.on("getUserName", id => {User.findOne({ _id: id }, (error, user) => {if (user) {client.emit("userName", user.user);} else {client.emit("serverError", { errorMsg: "找不到該用戶" });}});});// 接收信息client.on("sendMessage", data => {const { from, to, message } = data;const messageId = [from, to].sort().join("");const obj = {from,to,message,date: Date()};// 異步操作,找到聊天雙方async.parallel([function(callback) {User.findOne({ _id: from }, (error, user) => {if (error || !user) {callback(error, null);}callback(null, { from: user.user });});},function(callback) {User.findOne({ _id: to }, (error, user) => {if (error || !user) {callback(error, null);}callback(null, { to: user.user });});}],function(err, results) {if (err) {client.emit("error", { errorMsg: "找不到聊天對象" });} else {// 尋找該 messageId 是否存在Chat.findOne({messageId}).exec(function(err, doc) {// 不存在就自己創建保存if (!doc) {var chatModel = new Chat({messageId,bothSide: [{user: from,name: results[0].hasOwnProperty("from")? results[0].from: results[1].from},{user: to,name: results[0].hasOwnProperty("to")? results[0].to: results[1].to}],messages: [obj]});chatModel.save(function(err, chat) {if (err || !chat) {client.emit("serverError", { errorMsg: "后端出錯" });}if (clients[to]) {// 該 messageId 不存在就得發送發送方昵稱io.to(clients[to]).emit("message", {obj: chat.messages[chat.messages.length - 1],name: results[0].hasOwnProperty("from")? results[0].from: results[1].from});}});} else {doc.messages.push(obj);doc.save(function(err, chat) {if (err || !chat) {client.emit("serverError", { errorMsg: "后端出錯" });}if (clients[to]) {io.to(clients[to]).emit("message", {obj: chat.messages[chat.messages.length - 1]});}});}});}});});}); }; 復制代碼

課程中的這塊功能將會以重點來講述,并且會單獨開一個小視頻講解應用層及傳輸層必知知識。

課程相關

視頻預計會在 20 小時以上,但是本人畢竟不是專職講師,還是一線開發者,所以一周只會更新 2 - 3 小時視頻,視頻會在群內第一時間更新鏈接。

因為大家太熱情了,幾天不到加了600多人,所以還是開通了一個訂閱號用于發布視頻更新。

最后

這是項目地址,覺得不錯的可以給我點個 Star。

本篇文章也是我 18 年的第一篇博客,祝大家新年快樂,在新的一年學習更多的知識!

總結

以上是生活随笔為你收集整理的送给前端开发者的一份新年礼物的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。