redux的源碼解析

来源:https://www.cnblogs.com/createGod/archive/2018/05/17/9051166.html
-Advertisement-
Play Games

一、 redux出現的動機 1. Javascript 需要管理比任何時候都要多的state2. state 在什麼時候,由於什麼原因,如何變化已然不受控制。3. 來自前端開發領域的新需求4. 我們總是將兩個難以理清的概念混淆在一起:變化和非同步。5. Redux 視圖讓state 的變化變得可預測。 ...


一、 redux出現的動機

1. Javascript 需要管理比任何時候都要多的state
2. state 在什麼時候,由於什麼原因,如何變化已然不受控制。
3. 來自前端開發領域的新需求
4. 我們總是將兩個難以理清的概念混淆在一起:變化和非同步。
5. Redux 視圖讓state 的變化變得可預測。

二、 核心概念

 1. 想要更新state中的數據,你需要發起一個action,Action就是一個普通的JavaScript 對象用來描述發生了什麼。為了把actin 和state串起來開發一些函數,就是redcer。

三、 三大原則

1. 單一數據源 整個應用的state被存儲在一棵objecttree中, 並且這個 object tree 只 存在於一個唯一的store 中。

2. state 是只讀的,唯一改變state的方法就是觸發action,action 是一個用於描述已發 生事件的普通對象。(確保了視圖和網路請求不能直接修改state,只能表達想要修改的意圖)

3. 使用純函數來執行修改為了描述action如何改變state tree ,你需要編寫reducers。

四、 源碼解析

1. 入口文件index.js
import createStore from './createStore'
import combineReducers from './combineReducers'
import bindActionCreators from './bindActionCreators'
import applyMiddleware from './applyMiddleware'
import compose from './compose'
import warning from './utils/warning'
import __DO_NOT_USE__ActionTypes from './utils/actionTypes'

/*
 * This is a dummy function to check if the function name has been altered by minification.
 * If the function has been minified and NODE_ENV !== 'production', warn the user.
 */

// 判斷文件是否被壓縮了
function isCrushed() {}

if (
  process.env.NODE_ENV !== 'production' &&
  typeof isCrushed.name === 'string' &&
  isCrushed.name !== 'isCrushed'
) {
  warning(
    'You are currently using minified code outside of NODE_ENV === "production". ' +
      'This means that you are running a slower development build of Redux. ' +
      'You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ' +
      'or setting mode to production in webpack (https://webpack.js.org/concepts/mode/) ' +
      'to ensure you have the correct code for your production build.'
  )
}
/*
  從入口文件可以看出 redux 對外暴露了5個API。
  createStore ,
  combineReducers,
  bindActionCreators,
 applyMiddleware,
 compose,
 */
export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose,
  __DO_NOT_USE__ActionTypes
}
2. 對外暴露的第一個API createStore => createStore.js
  1 import $$observable from 'symbol-observable'
  2 
  3 import ActionTypes from './utils/actionTypes'
  4 import isPlainObject from './utils/isPlainObject'
  5 
  6 /**
  7  * Creates a Redux store that holds the state tree.
  8  * The only way to change the data in the store is to call `dispatch()` on it.
  9  *
 10  * There should only be a single store in your app. To specify how different
 11  * parts of the state tree respond to actions, you may combine several reducers
 12  * into a single reducer function by using `combineReducers`.
 13  *
 14  * @param {Function} reducer A function that returns the next state tree, given
 15  * the current state tree and the action to handle.
 16  *
 17  * @param {any} [preloadedState] The initial state. You may optionally specify it
 18  * to hydrate the state from the server in universal apps, or to restore a
 19  * previously serialized user session.
 20  * If you use `combineReducers` to produce the root reducer function, this must be
 21  * an object with the same shape as `combineReducers` keys.
 22  *
 23  * @param {Function} [enhancer] The store enhancer. You may optionally specify it
 24  * to enhance the store with third-party capabilities such as middleware,
 25  * time travel, persistence, etc. The only store enhancer that ships with Redux
 26  * is `applyMiddleware()`.
 27  *
 28  * @returns {Store} A Redux store that lets you read the state, dispatch actions
 29  * and subscribe to changes.
 30  */
 31 /*
 32   從源碼上可以看出 createStore 是一個函數。接收三個參數 reducer, preloadeState, enhancer
 33  */
 34 export default function createStore(reducer, preloadedState, enhancer) {
 35   // 如果 preloadeState 是一個函數 && enhancer未定義preloadeState 和 enhancer交換位置
 36   if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
 37     enhancer = preloadedState
 38     preloadedState = undefined
 39   }
 40   //
 41   if (typeof enhancer !== 'undefined') {
 42     if (typeof enhancer !== 'function') {
 43       throw new Error('Expected the enhancer to be a function.')
 44     }
 45     // 上面兩個判斷是為了確保 enchancer是個函數
 46     return enhancer(createStore)(reducer, preloadedState)
 47   }
 48 
 49   // reducer必須是 個函數,如果不是個函數給出友好的 提示
 50   if (typeof reducer !== 'function') {
 51     throw new Error('Expected the reducer to be a function.')
 52   }
 53 
 54   let currentReducer = reducer    // 把reducer 暫存起來
 55   let currentState = preloadedState // 把preloadeState暫存起來
 56   let currentListeners = []
 57   let nextListeners = currentListeners
 58   let isDispatching = false   //判斷是否正處於dispatch中
 59 
 60   // 如果 nextListeners 和 currrentListeners 都指向一個記憶體空間的時候, 深複製一份出來。確保兩個之間不會相互影響。
 61   function ensureCanMutateNextListeners() {
 62     if (nextListeners === currentListeners) {
 63       nextListeners = currentListeners.slice()
 64     }
 65   }
 66 
 67   /**
 68    * Reads the state tree managed by the store.
 69    *
 70    * @returns {any} The current state tree of your application.
 71    */
 72   // 獲取目前的 state的值。
 73   function getState() {
 74     if (isDispatching) {
 75       throw new Error(
 76         'You may not call store.getState() while the reducer is executing. ' +
 77           'The reducer has already received the state as an argument. ' +
 78           'Pass it down from the top reducer instead of reading it from the store.'
 79       )
 80     }
 81 
 82     return currentState
 83   }
 84 
 85   /**
 86    * Adds a change listener. It will be called any time an action is dispatched,
 87    * and some part of the state tree may potentially have changed. You may then
 88    * call `getState()` to read the current state tree inside the callback.
 89    *
 90    * You may call `dispatch()` from a change listener, with the following
 91    * caveats:
 92    *
 93    * 1. The subscriptions are snapshotted just before every `dispatch()` call.
 94    * If you subscribe or unsubscribe while the listeners are being invoked, this
 95    * will not have any effect on the `dispatch()` that is currently in progress.
 96    * However, the next `dispatch()` call, whether nested or not, will use a more
 97    * recent snapshot of the subscription list.
 98    *
 99    * 2. The listener should not expect to see all state changes, as the state
100    * might have been updated multiple times during a nested `dispatch()` before
101    * the listener is called. It is, however, guaranteed that all subscribers
102    * registered before the `dispatch()` started will be called with the latest
103    * state by the time it exits.
104    *
105    * @param {Function} listener A callback to be invoked on every dispatch.
106    * @returns {Function} A function to remove this change listener.
107    */
108   // 經典的訂閱函數
109   function subscribe(listener) {
110     if (typeof listener !== 'function') {
111       throw new Error('Expected the listener to be a function.')
112     }
113 
114     if (isDispatching) {
115       throw new Error(
116         'You may not call store.subscribe() while the reducer is executing. ' +
117           'If you would like to be notified after the store has been updated, subscribe from a ' +
118           'component and invoke store.getState() in the callback to access the latest state. ' +
119           'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
120       )
121     }
122     // 閉包的經典應用 每次訂閱一個事件的時候,都有一個內部狀態, 用於後續的取消訂閱
123     let isSubscribed = true
124     // 深複製一份監聽對象
125     ensureCanMutateNextListeners()
126     // 把每個監聽對象都放置於一個數組中,保存下來,(精華之處,對閉包的使用登峰造極)
127     nextListeners.push(listener)
128     // 當註冊一個監聽事件的返回一個函數,調用這個函數可以取消訂閱,具體操作方法就是從監聽的數組中移出掉。
129     return function unsubscribe() {
130       // 防止重覆取消訂閱
131       if (!isSubscribed) {
132         return
133       }
134 
135       if (isDispatching) {
136         throw new Error(
137           'You may not unsubscribe from a store listener while the reducer is executing. ' +
138             'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
139         )
140       }
141       // 對應上面那條,防止重覆取消訂閱
142       isSubscribed = false
143 
144       ensureCanMutateNextListeners()
145       const index = nextListeners.indexOf(listener)
146         // 刪除數組中某一項的方法 splice
147       nextListeners.splice(index, 1)
148     }
149   }
150 
151   /**
152    * Dispatches an action. It is the only way to trigger a state change.
153    *
154    * The `reducer` function, used to create the store, will be called with the
155    * current state tree and the given `action`. Its return value will
156    * be considered the **next** state of the tree, and the change listeners
157    * will be notified.
158    *
159    * The base implementation only supports plain object actions. If you want to
160    * dispatch a Promise, an Observable, a thunk, or something else, you need to
161    * wrap your store creating function into the corresponding middleware. For
162    * example, see the documentation for the `redux-thunk` package. Even the
163    * middleware will eventually dispatch plain object actions using this method.
164    *
165    * @param {Object} action A plain object representing “what changed”. It is
166    * a good idea to keep actions serializable so you can record and replay user
167    * sessions, or use the time travelling `redux-devtools`. An action must have
168    * a `type` property which may not be `undefined`. It is a good idea to use
169    * string constants for action types.
170    *
171    * @returns {Object} For convenience, the same action object you dispatched.
172    *
173    * Note that, if you use a custom middleware, it may wrap `dispatch()` to
174    * return something else (for example, a Promise you can await).
175    */
176   // 派發一個事件
177   function dispatch(action) {
178     // p、判斷action是否是個對象
179     if (!isPlainObject(action)) {
180       throw new Error(
181         'Actions must be plain objects. ' +
182           'Use custom middleware for async actions.'
183       )
184     }
185     // 嚴格控制 action 的書寫格式  { type: 'INCREMENT'}
186     if (typeof action.type === 'undefined') {
187       throw new Error(
188         'Actions may not have an undefined "type" property. ' +
189           'Have you misspelled a constant?'
190       )
191     }
192 
193     if (isDispatching) {
194       throw new Error('Reducers may not dispatch actions.')
195     }
196     // isDipatching 也是閉包的經典用法
197       /*
198         觸發 dispatch的時候 把 isDispatching 改為 true。 照應全篇中對 dispatching 正在觸發的時候的判斷
199         finally 執行完畢的時候 置為 false
200        */
201     try {
202       isDispatching = true
203       // 獲取最新 的state 值。 currentState 經典之處閉包
204       currentState = currentReducer(currentState, action)
205     } finally {
206       isDispatching = false
207     }
208 
209     // 對監聽對象從新賦值 其實裡面每個listener都是一個函數。 於subscribe相對應
210     // 當每發生一個dispatch 事件的時候, 都迴圈調用,觸發監聽事件
211     const listeners = (currentListeners = nextListeners)
212     for (let i = 0; i < listeners.length; i++) {
213       const listener = listeners[i]
214       listener()
215     }
216 
217     return action
218   }
219 
220   /**
221    * Replaces the reducer currently used by the store to calculate the state.
222    *
223    * You might need this if your app implements code splitting and you want to
224    * load some of the reducers dynamically. You might also need this if you
225    * implement a hot reloading mechanism for Redux.
226    *
227    * @param {Function} nextReducer The reducer for the store to use instead.
228    * @returns {void}
229    */
230   // 替換 reducer 用 新的 reducer替換以前的reducer 參數同樣必須是函數
231   function replaceReducer(nextReducer) {
232     if (typeof nextReducer !== 'function') {
233       throw new Error('Expected the nextReducer to be a function.')
234     }
235 
236     currentReducer = nextReducer
237     dispatch({ type: ActionTypes.REPLACE })
238   }
239 
240   /**
241    * Interoperability point for observable/reactive libraries.
242    * @returns {observable} A minimal observable of state changes.
243    * For more information, see the observable proposal:
244    * https://github.com/tc39/proposal-observable
245    */
246   // 觀察模式
247   function observable() {
248     const outerSubscribe = subscribe
249     return {
250       /**
251        * The minimal observable subscription method.
252        * @param {Object} observer Any object that can be used as an observer.
253        * The observer object should have a `next` method.
254        * @returns {subscription} An object with an `unsubscribe` method that can
255        * be used to unsubscribe the observable from the store, and prevent further
256        * emission of values from the observable.
257        */
258       subscribe(observer) {
259         if (typeof observer !== 'object' || observer === null) {
260           throw new TypeError('Expected the observer to be an object.')
261         }
262 
263         function observeState() {
264           if (observer.next) {
265             observer.next(getState())
266           }
267         }
268 
269         observeState()
270         const unsubscribe = outerSubscribe(observeState)
271         return { unsubscribe }
272       },
273 
274       [$$observable]() {
275         return this
276       }
277     }
278   }
279 
280   // When a store is created, an "INIT" action is dispatched so that every
281   // reducer returns their initial state. This effectively populates
282   // the initial state tree.
283     // 觸發一state 樹
284   dispatch({ type: ActionTypes.INIT })
285   /*
286      由此可以看出 調用 createStore(); 後。對外暴露的方法
287      1. dispatch
288      2. subscribe
289      3. getState
290      4. replaceReducer
291      5.觀察模式
292      const store = createStore(reducer, preloadedState, enchancer);
293      store.dispatch(action);
294      store.getState(); // 為什麼這個方法能夠獲得 state的值。因為 currentState 的閉包實現。
295      store.subscribe(() => console.log(store.getState()));
296      store.replaceReducer(reducer);
297      總結:縱觀createStore方法的實現,其實都是建立在閉包的基礎之上。可謂是把閉包用到了極致。
298    */
299   return {
300     dispatch,
301     subscribe,
302     getState,
303     replaceReducer,
304     [$$observable]: observable
305   }
306 }
3. combineReducers.js
  1 import ActionTypes from './utils/actionTypes'
  2 import warning from './utils/warning'
  3 import isPlainObject from './utils/isPlainObject'
  4 
  5 function getUndefinedStateErrorMessage(key, action) {
  6   const actionType = action && action.type
  7   const actionDescription =
  8     (actionType && `action "${String(actionType)}"`) || 'an action'
  9 
 10   return (
 11     `Given ${actionDescription}, reducer "${key}" returned undefined. ` +
 12     `To ignore an action, you must explicitly return the previous state. ` +
 13     `If you want this reducer to hold no value, you can return null instead of undefined.`
 14   )
 15 }
 16 
 17 function getUnexpectedStateShapeWarningMessage(
 18   inputState,
 19   reducers,
 20   action,
 21   unexpectedKeyCache
 22 ) {
 23   const reducerKeys = Object.keys(reducers)
 24   const argumentName =
 25     action && action.type === ActionTypes.INIT
 26       ? 'preloadedState argument passed to createStore'
 27       : 'previous state received by the reducer'
 28 
 29   if (reducerKeys.length === 0) {
 30     return (
 31       'Store does not have a valid reducer. Make sure the argument passed ' +
 32       'to combineReducers is an object whose values are reducers.'
 33     )
 34   }
 35 
 36   if (!isPlainObject(inputState)) {
 37     return (
 38       `The ${argumentName} has unexpected type of "` +
 39       {}.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] +
 40       `". Expected argument to be an object with the following ` +
 41       `keys: "${reducerKeys.join('", "')}"`
 42     )
 43   }
 44 
 45   const unexpectedKeys = Object.keys(inputState).filter(
 46     key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key]
 47   )
 48 
 49   unexpectedKeys.forEach(key => {
 50     unexpectedKeyCache[key] = true
 51   })
 52 
 53   if (action && action.type === ActionTypes.REPLACE) return
 54 
 55   if (unexpectedKeys.length > 0) {
 56     return (
 57       `Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` +
 58       `"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` +
 59       `Expected to find one of the known reducer keys instead: ` +
 60       `"${reducerKeys.join('", "')}". Unexpected keys will be ignored.`
 61     )
 62   }
 63 }
 64 
 65 function assertReducerShape(reducers) {
 66   Object.keys(reducers).forEach(key => {
 67     const reducer = reducers[key]
 68     const initialState = reducer(undefined, { type: ActionTypes.INIT })
 69 
 70     if (typeof initialState === 'undefined') {
 71       throw new Error(
 72         `Reducer "${key}" returned undefined during initialization. ` +
 73           `If the state passed to the reducer is undefined, you must ` +
 74           `explicitly return the initial state. The initial state may ` +
 75           `not be undefined. If you don't want to set a value for this reducer, ` +
 76           `you can use null instead of undefined.`
 77       )
 78     }
 79 
 80     if (
 81       typeof reducer(undefined, {
 82         type: ActionTypes.PROBE_UNKNOWN_ACTION()
 83       }) === 'undefined'
 84     ) {
 85       throw new Error(
 86         `Reducer "${key}" returned undefined when probed with a random type. ` +
 87           `Don't try to handle ${
 88             ActionTypes.INIT
 89           } or other actions in "redux/*" ` +
 90           `namespace. They are considered private. Instead, you must return the ` +
 91           `current state for any unknown actions, unless it is undefined, ` +
 92           `in which case you must return the initial state, regardless of the ` +
 93           `action type. The initial state may not be undefined, but can be null.`
 94       )
 95     }
 96   })
 97 }
 98 
 99 /**
100  * Turns an object whose values are different reducer functions, into a single
101  * reducer function. It will call every child reducer, and gather their results
102  * into a single state object, whose keys correspond to the keys of the passed
103  * reducer functions.
104  *
105  * @param {Object} reducers An object whose values correspond to different
106  * reducer functions that need to be combined into one. One handy way to obtain
107  * it is to use ES6 `import * as reducers` syntax. The reducers may never return
108  * undefined for any action. Instead, they should return their initial state
109  * if the state passed to them was undefined, and the current state for any
110  * unrecognized action.
111  *
112  * @returns {Function} A reducer function that invokes every reducer inside the
113  * passed object, and builds a state object with the same shape.
114  */
115 
116 /*
117   combineReducers 顧名思義就是合併reduces的一個方法。
118   1. 為了項目便於維護與管理我們就需要拆按模塊拆分reducers。
119   2. 而combineReducers就是為瞭解決這個的問題的。
120 
121  */
122 export default function combineReducers(reducers) { // 參數reducers 是一個對象
123   const reducerKeys = Object.keys(reducers) // 獲取reducers的k
124   const finalReducers = {}
125   for (let i = 0; i < reducerKeys.length; i++) {
126     const key = reducerKeys[i]
127 
128     if (process.env.NODE_ENV !== 'production') {
129       if (typeof reducers[key] === 'undefined') {
130         warning(`No reducer provided for key "${key}"`)
131       }
132     }
133 
134     // 深複製一份reducers出來, 防止後續操作出現不可控因素
135     if (typeof reducers[key] === 'function') {
136       finalReducers[key] = reducers[key]
137     }
138   }
139   const finalReducerKeys = Object.keys(finalReducers)
140 
141   let unexpectedKeyCache
142   if (process.env.NODE_ENV !== 'production') {
143     unexpectedKeyCache = {}
144   }
145 
146   let shapeAssertionError
147   try {
148     assertReducerShape(finalReducers)
149   } catch (e) {
150     shapeAssertionError = e
151   }
152   // 閉包的運用, 把合併的 reducer保存下來。
153   return function combination(state = {}, action) {
154     if (shapeAssertionError) {
155       throw shapeAssertionError
156     }
157 
158     if (process.env.NODE_ENV !== 'production') {
159       const warningMessage = getUnexpectedStateShapeWarningMessage(
160         state,
161         finalReducers,
162         action,
163         unexpectedKeyCache
164       )
165       if (warningMessage) {
166         warning(warningMessage)
167       }
168     }
169 
170     let hasChanged = false
171     const nextState = {}
172     for (let i = 0; i < finalReducerKeys.length; i++) {
173       const key = finalReducerKeys[i]
174       const reducer = finalReducers[key]
175       const previousStateForKey = state[key]
176       // 把合併的時候的key值作為Key值為標準。 在迴圈遍歷的時候取出對應的 reducers 觸發 reducer函數。
177       /*
178         其實對應的createStore.js中的
179         try {
180           isDispatching = true
181           // 獲取最新 的state 值。 currentState 經典之處閉包
182           currentState = currentReducer(currentState, action)
183         } finally {
184           isDispatching = false
185         }  
186        */
187       const nextStateForKey = reducer(previousStateForKey, action)
188       if (typeof nextStateForKey === 'undefined') {
189         const errorMessage = getUndefinedStateErrorMessage(key, action)
190         throw new Error(errorMessage)
191       }
192       nextState[key] = nextStateForKey
193       hasChanged = hasChanged || nextStateForKey !== previousStateForKey
194     }
195     return hasChanged ? nextState : state
196   }
197 }
198 /**
199  * 使用方法
200  * const reducers = combineReducers({ reducer1, reducer2 });
201  * const store = createStore(reducers, preloadedState, enchancer);
202  */
4. bindActionCreators.js
 1 // 主要這個函數
 2 function bindActionCreator(actionCreator, dispatch) {
 3   return function() {
 4     return dispatch(actionCreator.apply(this, arguments))
 5   }
 6 }
 7 
 8 /**
 9 
              
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 嘿,Goodgirl and GoodBoy,點進來了就看完點個贊再go. Vue這個框架就不簡單介紹了,它最大的特性就是數據的雙向綁定以及虛擬dom.核心就是用數據來驅動視圖層的改變.先看一段代碼. 一、示例 var vm = new Vue({ data: { obj: { a: 1 } }, ...
  • 在這裡呢,我們先來說下關於完美運動框架的封裝思路。 想讓一個物體運動呢,我們必須給那個物體加上定位屬性;其次想讓一個物體自動運動的話必須用到定時器;知道了這個後,基本上就差不多做完了(哈哈,給你個小安慰)! 首先在封裝框架之前我們得封裝一下獲取非行間樣式,這樣的話我們就不單單局限於(offsetwi ...
  • 現在前端全棧裡面有一種技術棧比較火 前端使用 vue 或者react 後端使用 koa2 mysql資料庫 或者mongdb做數據儲存 但是基本這樣的全棧教程 都要收費 收費就收費吧 但是 有沒有遇到非常好的教程 於是 準備硬著頭皮看別人項目的源碼 自己摸索 一步一步完成 koa + mongdb的 ...
  • 恢復內容開始 2018-05-17 17:49:11 通過<a>標簽來創建HTML文檔里的鏈接 有兩種使用 <a> 標簽的方式: 1. 通過使用 href 屬性 - 創建指向另一個文檔的鏈接或者網址; 2. 通過使用 name 屬性 - 創建文檔內的書簽。 代碼舉例: 1. <a herf = "h ...
  • 1. Vue.js 介紹 Vue,讀音 /vjuː/,類似於 view),是一套用於構建用戶界面的漸進式框架(重點在於視圖層)。 作者:尤雨溪 註:學習 vue.js 時,一定要拋棄 jQuery 的思想(jQuery的特點在於強大的選擇器、dom操作,而vue.js 的特點在於數據驅動來實現數據和 ...
  • 今天才知道原來position加上上下左右可以控制的到div的寬度的 比如我現在有一個需要 這樣一個需求 現在是紅色部分50px 但是你可能不知道剩下的藍色部分的高度是多少 藍色部分要怎麼填滿剩餘的高度呢 這時候left,right,top,bottom,就很有用了 直接給藍色的div:positi ...
  • 前言: 最近剛剛完成項目,空閑一段時間,想起之前有被問起怎麼對前端進行性能優化,自己也是腦中零零散散的總不成體系,現特來總結,歡迎補充指教。 1、整體資源 (1)js、css源碼壓縮 (2)css文件放到文檔頂部,js 文件放到文檔底部 因為瀏覽器渲染網頁是自上而下的,用戶第一眼見到的是頁面,先載入 ...
  • scoped css "官方文檔" scoped css可以直接在能跑起來的vue項目中使用。 使用方法: 使用scoped劃分本地樣式的結果編譯結果如下: 即在元素中添加了一個唯一屬性用來區分。 缺點 一、如果用戶在別處定義了相同的類名,也許還是會影響到組件的樣式。 二、根據css樣式優先順序的特性 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...