主頁 > 前端設計 > Redux從入門到進階,看這一篇就夠了!

Redux從入門到進階,看這一篇就夠了!

2021-10-25 07:10:29 前端設計

Redux,帶你從入門到進階

  • 🌂序言
  • ??一、基礎知識
    • 1、Redux概念簡述
    • 2、Redux的作業流程
  • 🎃二、使用Antd實作TodoList頁面布局
    • 1、在專案中使用Antd
    • 2、使用Antd實作TodoList的基本布局
    • 3、創建redux中的store
      • (1)創建store
      • (2)在專案中使用store
  • 🧵三、Action和Reducer的撰寫 - 增添功能
    • 1、主體頁面內容改造
    • 2、改變action中的資料
    • 3、store資料改造
  • 🧶四、使用Redux實作TodoList的洗掉功能
    • 1、對組件進行事件系結
    • 2、在reducer中進行資料通信
  • 👓五、邏輯歸納
    • 1、ActionTypes的拆分
    • 2、使用actionCreator統一創建action
  • 👔六、Redux的一些總結
    • 1、Redux設計和使用的三項原則
    • 2、Redux的核心API
  • 👝七、進階組件的拆分
    • 1、UI組件和容器組件的拆分
    • 2、無狀態組件
  • 🎩八、Redux發起異步請求
    • 1、Redux中發送異步請求資料
    • 2、Redux-thunk中間件
      • (1)解決什么問題
      • (2)如何使用
      • (3)為什么要使用 redux-thunk ?
      • (4)什么是 Redux-thunk 中間件?
  • 💼九、Redux的其他中間件
    • 1、Redux-logger
    • 2、Redux-saga
      • (1)Redux-saga是什么
      • (2)Redux-saga如何使用
  • 🛵十、React-Redux
    • 1、React-Redux是什么
    • 2、React-Redux的使用
      • (1)安裝React-Redux
      • (2)專案目錄
      • (3)核心內容
  • 🚦十一、結束語
  • 🐣彩蛋 One More Thing
    • (:往期推薦
    • (:番外篇

🌂序言

大家都知道, react單向資料流,所以它傳遞資料也較為簡單,父子之間的關系也較為明確,但是呢,如果我們要做更多復雜資料的傳遞,單單使用 react 是完全不夠的,因此,我們需要用到 redux 來做更為復雜的資料傳遞,

那在下面的這篇文章中,將從入門到進階,講解 redux 的作業流程,

叮!開始 redux 之旅吧~👏

??一、基礎知識

1、Redux概念簡述

對于 react 來說,它是一個非視圖層的輕量級框架,如果要用它來傳遞資料的話,則要先父傳子,然后再慢慢地一層一層往上傳遞,

但如果用 redux 的話,假設我們想要某個組件的資料,那這個組件的資料則會通過 redux 來存放到 store 中進行管理,之后呢,通過 store ,再來將資料一步步地往下面的組件進行傳遞,

值得注意的是,我們可以視 ReduxReducerFlux 的結合,

2、Redux的作業流程

Redux ,實際上就是一個資料層的框架,它把所有的資料都放在了 store 之中,我們先來看一張圖:

Redux的作業流程

大家可以看到中間的 store ,它里面就存放著所有的資料,繼續看 store 向下的箭頭,然后呢,每個組件都要向 store 里面去拿資料,

我們用一個例子來梳理整張圖,具體如下:

  • ①整張圖上有一個 store ,它存放著所有的資料,也就是存盤資料的公共區域
  • ②每個組件,都要從 store 里面拿資料;
  • ③假設現在有一個場景,模擬我們要在圖書館里面借書,那么我們可以把 react Component 理解為借書人,之后呢,借書人要去找圖書館管理員才能借到這本書,而借書這個程序中資料的傳遞,就可以把它視為是 Action Creators ,可以理解為 “你想要借什么書” 這句話,
  • Action Creatures 去到 store ,這個時候我們把 store 當做是圖書館管理員,但是,圖書館管理員是沒有辦法記住所有圖書的資料情況的,一般來說,它都需要一個記錄本,你想要借什么樣的書,那么她就先查一下;又或者你想要還什么書,她也要查一下,需要放回什么位置上,
  • ⑤這個時候就需要跟 reducers 去通信,我們可以把 reducers 視為是一個記錄本,圖書館管理員用這個記錄本來記錄需要的資料,管理員 store 通過 reducer 知道了應該給借書人 Components 什么樣的資料,

🎃二、使用Antd實作TodoList頁面布局

1、在專案中使用Antd

打開 antdesign 的官網👉antd官網傳送門,我們先來在專案中引入它,具體步驟如下:

第一步,安裝 antd命令如下:

npm install antd --save

第二步,引入樣式,代碼如下:

import 'antd/dist/antd.css'; // or 'antd/dist/antd.less'

2、使用Antd實作TodoList的基本布局

首先,我們在專案的 src 檔案夾下創建一個新的檔案,命名為 TodoList.js具體代碼如下:

import React, { Component } from 'react';
import 'antd/dist/antd.css';
import { Input, Button, List } from 'antd';

const data = [
  'Racing car sprays burning fuel into crowd.',
  'Japanese princess to wed commoner.',
  'Australian walks 100km after outback crash.',
  'Man charged over missing wedding girl.',
  'Los Angeles battles huge wildfires.',
];

class TodoList extends Component {
  render() {
    return (
      <div style={{marginTop: '10px', marginLeft: '10px'}}>
        <div>
          <Input placeholder="todo info" style={{ width: '300px' }} />
          <Button type="primary">提交</Button>
        </div>
        <List
          bordered
          dataSource={data}
          renderItem={item => <List.Item>{item}</List.Item>}
        />
      </div>
    )
  }
}

export default TodoList;

此時瀏覽器的顯示效果為:

antd布局todoList

3、創建redux中的store

(1)創建store

接下來我們來創建專案中的 store ,首先在專案的 src 檔案夾下創建一個新的檔案夾,命名為 store ,接著,我們在 store 檔案夾下面,創建一個新的檔案,命名為 index.js具體代碼如下:

import { createStore } from "redux";
import reducer from './reducer';

const store = createStore(reducer);

export default store;

然后呢,繼續在 store 檔案夾下面創建一個新的檔案,命名為 reducer.js具體代碼如下:

const defaultStore = {
  inputValue: '',
  list: []
};

export default (state = defaultStore, action) => {
  return state;
}

由此,我們就創建完成了專案中的 store

(2)在專案中使用store

創建完 store 之后,我們先初步在專案中使用這個 store ,以便于看看效果,先來添加 store 中的資料,首先改造在 store 中的 reducer.js 檔案,具體代碼如下:

const defaultStore = {
  inputValue: '123',
  list: [1, 2]
};

export default (state = defaultStore, action) => {
  return state;
}

之后改造 TodoList.js具體代碼如下:

import React, { Component } from 'react';
import 'antd/dist/antd.css';
import { Input, Button, List } from 'antd';
import store from './store'; 

class TodoList extends Component {
  constructor(props) {
    super(props);
      this.state = store.getState()
  }
  render() {
    return (
      <div style={{marginTop: '10px', marginLeft: '10px'}}>
        <div>
          <Input placeholder={this.state.inputValue} style={{ width: '300px' }} />
          <Button type="primary">提交</Button>
        </div>
        <List
          bordered
          dataSource={this.state.list}
          renderItem={item => <List.Item>{item}</List.Item>}
        />
      </div>
    )
  }
}

export default TodoList;

此時瀏覽器的顯示效果為:

todoList接收來自store的資料

🧵三、Action和Reducer的撰寫 - 增添功能

1、主體頁面內容改造

接下來,我們使用 actionreducer ,來對這個組件的資料進行前后傳遞,首先,先來改造 TodoList.js 檔案,具體代碼如下:

import React, { Component } from 'react';
import 'antd/dist/antd.css';
import { Input, Button, List } from 'antd';
import store from './store'; 

class TodoList extends Component {
  constructor(props) {
    super(props);
    this.state = store.getState()
    this.handleInputChange = this.handleInputChange.bind(this)
    this.handleStoreChange = this.handleStoreChange.bind(this)
    this.handleBtnClick = this.handleBtnClick.bind(this)
    store.subscribe(this.handleStoreChange)
  }
  render() {
    return (
      <div style={{marginTop: '10px', marginLeft: '10px'}}>
        <div>
          <Input
            value={this.state.inputValue}
            placeholder="todo info"
            style={{ width: '300px', marginRight: '10px'}}
            onChange={this.handleInputChange}
          />
          <Button type="primary" onClick={this.handleBtnClick}>提交</Button>
        </div>
        <List
          style={{marginTop: '10px', width: '300px'}}
          bordered
          dataSource={this.state.list}
          renderItem={item => <List.Item>{item}</List.Item>}
        />
      </div>
    )
  }

  handleInputChange(e) {
    // 在react中,action是一個物件的形式
    // type旨在告訴react說,你幫我去改變input的值,這個值是下面的value,也就是e.target.value
    const action = {
      type: 'change_input_value',
      value: e.target.value
    }
    store.dispatch(action)
    // console.log(e.target.value)
  }

  handleStoreChange() {
    // 當感知到store的資料發生變化時,那么就去呼叫store.getState方法,從store里面再重新取一次資料,
    // 然后去呼叫setState,替換掉當前store里面的資料
    this.setState(store.getState())
  }

  handleBtnClick() {
    const action = {
      type: 'add_todo_item'
    }
    store.dispatch(action)
  }
}

export default TodoList;

接下來我們來分析以上代碼,首先,每一個動作分別會先去系結對應的事件,之后呢,在事件里面,去創造 action ,而對于創造的 action 來說,它旨在告訴 react ,讓 react 去幫忙 action 去改變某個值,而這個值就是它系結的 value

最后, action 要做的事情結束了,那么它的資料就需要去存盤到 store 里面,于是通過 store.dispatch(action) 來進行處理,將 action 的資料傳遞到 store 里面,

2、改變action中的資料

對于 action 一開始的值來說,它是固定的,但有時候我們是想要去修改action中的值,這個時候就需要用到 reducer ,現在,我們來改造下 reducer.js 檔案,讓 input 框可以自由的輸入值,同時,點擊提交按鈕之后,進行串列的增添操作,具體代碼如下:

const defaultStore = {
  inputValue: '123',
  list: [1, 2]
};

// reducer 可以接收state,但是絕不能修改state
const reducer = (state = defaultStore, action) => {
  if (action.type === 'change_input_value') {
    const newState = JSON.parse(JSON.stringify(state));
    newState.inputValue = action.value;
    return newState;
  }
  if (action.type === 'add_todo_item') {
    const newState = JSON.parse(JSON.stringify(state));
    newState.list.push(newState.inputValue);
    newState.inputValue = '';
    console.log(newState);
    return newState;
  }
  return state;
}
export default reducer;

3、store資料改造

下面,我們來看下 store 檔案夾下 index.js 的內容,我們需要對其進行簡單的改造,具體代碼如下:

import { createStore } from "redux";
import reducer from './reducer';

const store = createStore(
  reducer,
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);

export default store;

除了 reducer 之外,我們還要將 window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() 給傳遞進去并呼叫這個方法,

最后,我們來看下瀏覽器的顯示效果:

action和reducer傳遞資料

🧶四、使用Redux實作TodoList的洗掉功能

1、對組件進行事件系結

上面我們實作了增添功能,那么現在,我們繼續來實作洗掉功能,實作每點擊每一項時,能夠洗掉點擊項的資料,先來在 TodoList.js 檔案中系結對應的事件,具體代碼如下:

import React, { Component } from 'react';
import 'antd/dist/antd.css';
import { Input, Button, List } from 'antd';
import store from './store'; 

class TodoList extends Component {
  constructor(props) {
    // 此處省略上述已有代碼
  }
  render() {
    return (
      {/* 此處省略上述已有代碼 */}
        <List
          style={{marginTop: '10px', width: '300px'}}
          bordered
          dataSource={this.state.list}
          renderItem={(item, index) => <List.Item onClick={this.handleItemDelete.bind(this, index)}>{item}</List.Item>}
        />
      </div>
    )
  }

  // 此處省略上述已有代碼
  
  handleItemDelete(index) {
    const action = {
      type: 'delete_todo_item',
      index
    }
    store.dispatch(action);
  }
}

export default TodoList;

2、在reducer中進行資料通信

接著,我們在 reducer.js 檔案中,對資料進行通信,具體代碼如下:

const defaultStore = {
  inputValue: '123',
  list: [1, 2]
};

// reducer 可以接收state,但是絕不能修改state
const reducer = (state = defaultStore, action) => {
  // 此處省略上述已有代碼
    
  if (action.type === 'delete_todo_item') {
    const newState = JSON.parse(JSON.stringify(state));
    newState.list.splice(action.index, 1);
    return newState;
  }
  return state;
}
export default reducer;

現在,我們來看下瀏覽器的顯示效果:

洗掉功能

👓五、邏輯歸納

1、ActionTypes的拆分

在上面的 TodoList.js 中,大家可以看到,我們會頻繁地去操作 action ,同時,假設說其中的 type 如果我們稍微寫錯了一個字母,那排錯的程序總是不好定位的,

因此,我們要來做的一件事情就是 ActionTypes 的拆分,

首先,我們在 store 檔案夾下新增一個檔案,命名為 actionTypes.js具體代碼如下:

export const CHANGE_INPUT_VALUE = 'change_input_value';
export const ADD_TODO_ITEM = 'add_todo_item';
export const DELETE_TODO_ITEM = 'delete_todo_item';

其次,改造 TodoList.js 下的內容,具體代碼如下:

import {
  CHANGE_INPUT_VALUE,
  ADD_TODO_ITEM,
  DELETE_TODO_ITEM
} from './store/actionTypes'

class TodoList extends Component {

  handleInputChange(e) {
    const action = {
      type: CHANGE_INPUT_VALUE,
      value: e.target.value
    }
    store.dispatch(action)
  }

  handleStoreChange() {
    this.setState(store.getState())
  }

  handleBtnClick() {
    const action = {
      type: ADD_TODO_ITEM
    }
    store.dispatch(action)
  } 

  handleItemDelete(index) {
    const action = {
      type: DELETE_TODO_ITEM,
      index
    }
    store.dispatch(action);
  }
}

export default TodoList;

最后,改造 reducer.js 檔案,具體代碼如下:

import {
  CHANGE_INPUT_VALUE,
  ADD_TODO_ITEM,
  DELETE_TODO_ITEM
} from './actionTypes';

const defaultStore = {
  inputValue: '123',
  list: [1, 2]
};

const reducer = (state = defaultStore, action) => {
  if (action.type === CHANGE_INPUT_VALUE) {
    const newState = JSON.parse(JSON.stringify(state));
    newState.inputValue = action.value;
    return newState;
  }
  if (action.type === ADD_TODO_ITEM) {
    const newState = JSON.parse(JSON.stringify(state));
    newState.list.push(newState.inputValue);
    newState.inputValue = '';
    console.log(newState);
    return newState;
  }
  if (action.type === DELETE_TODO_ITEM) {
    const newState = JSON.parse(JSON.stringify(state));
    newState.list.splice(action.index, 1);
    return newState;
  }
  return state;
}
export default reducer;

通過將 change_input_valueadd_todo_itemdelete_todo_item 進行整合,將其整合到 actionTypes.js 檔案下,這樣,如果我們遇到字母寫錯的情況下,也能夠更好的進行排錯,

2、使用actionCreator統一創建action

在上面的 TodoList.js 中,大家可以看到,對于幾個系結的事件來說,我們總是要頻繁的去創建 action ,重復性地操作是在程式中最忌諱的一個事情,因此呢,我們要使用 actionCreator ,來對 action 進行統一管理,使得邏輯更加地統一完整,

首先,我們在 store 檔案夾下新創建一個檔案,命名為 actionCreators.js具體代碼如下:

import { CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM } from "./actionTypes";

export const getInputChangeAction = (value) => ({
  type: CHANGE_INPUT_VALUE,
  value: value
});

export const getAddItemAction = (value) => ({
  type: ADD_TODO_ITEM
});

export const getDeleteItemAction = (index) => ({
  type: DELETE_TODO_ITEM,
  index: index
});

繼續,我們來改造 TodoList.js具體代碼如下:

import { getInputChangeAction, getAddItemAction, getDeleteItemAction } from './store/actionCreators'

class TodoList extends Component {

  handleInputChange(e) {
    const action = getInputChangeAction(e.target.value);
    store.dispatch(action)
  }

  handleBtnClick() {
    const action = getAddItemAction();
    store.dispatch(action)
  } 

  handleItemDelete(index) {
    const action = getDeleteItemAction(index);
    store.dispatch(action);
  }
}

export default TodoList;

通過將 action 中的操作統一抽離到 actionCreators.js 當中,使得最終的邏輯更加的統一,

👔六、Redux的一些總結

講到這里,我們對上面的一些知識點進行歸納總結,具體如下

1、Redux設計和使用的三項原則

Redux 的設計和使用遵循以下三大原則:

  • store 必須是唯一的👉即整個應用之中必須且只能有一個 store
  • 只有 store 能夠改變自己的內容👉即 store 不是 reducer 去更新的,而是 store 在拿到 reducer 的資料之后,自己對自己的資料進行一次更新;因此,我們回到上面的 reducer.js 檔案,在 react 中,是不允許 state.inputValue === 某個值 之類的事情發生的哦,也就是說不能對其直接進行賦值,
  • Reducer 必須是純函式👉所謂純函式,即給定固定的輸入,就一定有固定的輸出,而且不會產生任何的副作用,回到我們上面的 reducer.js 檔案,大家可以看到, state 是固定的, action 也是固定的,那么最侄訓傳的 newState 自然也就是固定的,

2、Redux的核心API

我們再來復習 Redux 的幾個核心 API先看下圖:

Redux的核心API

現在來回顧下這幾個核心 API 的作用,具體如下:

  • createStore —— 可以幫助我們創建一個 store
  • store.dispatch —— dispatch 方法幫助我們派發 action ,同時,這個 action 會傳遞給 store
  • store.getState —— getState 方法幫助我們獲取到所有的資料;
  • store.subscribe —— subscribe 幫助我們訂閱 store 的改變,只要 store 發生改變,store.subscribe 接收的回呼函式就會被執行,

👝七、進階組件的拆分

1、UI組件和容器組件的拆分

在上面的代碼中,我們已經基本完成了 TodoList 的功能,但是呢,大家有沒有發現,在 TodoList.js 檔案中,頁面的渲染和頁面的邏輯撰寫是放在一起的,

往往在實際開發中,我們都會直接把 UI 組件容器組件給拆分開來,其中, UI 組件專門用于負責頁面的渲染,而容器組件用于負責頁面的邏輯

下面我們來對其進行拆分,首先,我們現在 src 檔案夾下新增一個檔案,命名為 TodoListUI.js具體代碼如下:

import React, { Component } from 'react';
import 'antd/dist/antd.css';
import { Input, Button, List } from 'antd';

class TodoListUI extends Component {
    render() {
        return (
            <div style={{ marginTop: '10px', marginLeft: '10px' }}>
                <div>
                    <Input
                        value={this.props.inputValue}
                        placeholder="todo info"
                        style={{ width: '300px', marginRight: '10px' }}
                        onChange={this.props.handleInputChange}
                    />
                    <Button type="primary" onClick={this.props.handleBtnClick}>提交</Button>
                </div>
                <List
                    style={{ marginTop: '10px', width: '300px' }}
                    bordered
                    dataSource={this.props.list}
                    renderItem={(item, index) => <List.Item onClick={() => { this.props.handleItemDelete(index) }}>{item}</List.Item>}
                />
            </div>
        )
    }
}

export default TodoListUI;

繼續,我們來改造 TodoList.js 檔案的內容,具體代碼如下:

import React, { Component } from 'react';

import store from './store';
import { getInputChangeAction, getAddItemAction, getDeleteItemAction } from './store/actionCreators';
import TodoListUI from './TodoListUI';

class TodoList extends Component {
    constructor(props) {
        super(props);
        this.state = store.getState()
        this.handleInputChange = this.handleInputChange.bind(this)
        this.handleStoreChange = this.handleStoreChange.bind(this)
        this.handleBtnClick = this.handleBtnClick.bind(this)
        this.handleItemDelete = this.handleItemDelete.bind(this)
        store.subscribe(this.handleStoreChange)
    }
    render() {
        return (
            <TodoListUI
                inputValue={this.state.inputValue}
                list={this.state.list}
                handleInputChange={this.handleInputChange}
                handleBtnClick={this.handleBtnClick}
                handleItemDelete={this.handleItemDelete}
            />
        )
    }

    handleInputChange(e) {
        const action = getInputChangeAction(e.target.value);
        store.dispatch(action)
    }

    handleStoreChange() {
        this.setState(store.getState())
    }

    handleBtnClick() {
        const action = getAddItemAction();
        store.dispatch(action)
    }

    handleItemDelete(index) {
        const action = getDeleteItemAction(index);
        store.dispatch(action);
    }
}

export default TodoList;

大家可以看到,我們把頁面的內容給單獨抽離出來放到 TodoListUI.js 檔案當中,讓它只做渲染這一件事情,這樣,我們就成功的把 UI 組件和邏輯組件進行拆分,

2、無狀態組件

有了 UI 組件之后,我們再來看另外一種組件,無狀態組件,所謂無狀態組件,就是整個頁面什么邏輯都沒有,只有一個 render 函式時,我們可以把它稱之為是一個無狀態組件,

那無狀態組件怎么定義呢??

我們可以定義一個函式,這個函式接收一個引數,propsTodoListUI.js 檔案的具體代碼如下:

import React from 'react';
import { Input, Button, List } from 'antd';

const TodoListUI = (props) => {
    return (
        <div style={{ marginTop: '10px', marginLeft: '10px' }}>
            <div>
                <Input
                    value={props.inputValue}
                    placeholder="todo info"
                    style={{ width: '300px', marginRight: '10px' }}
                    onChange={props.handleInputChange}
                />
                <Button type="primary" onClick={props.handleBtnClick}>提交</Button>
            </div>
            <List
                style={{ marginTop: '10px', width: '300px' }}
                bordered
                dataSource={props.list}
                renderItem={(item, index) => <List.Item onClick={() => { props.handleItemDelete(index) }}>{item}</List.Item>}
            />
        </div>
    )
}

export default TodoListUI;

當一個普通函式只有 render 函式的時候,我們完全可以通過一個無狀態的組件來替換掉這個普通的組件,那為什么要做這樣子的替換呢?

如果我們改造為只有一個函式的時候,那么程式就只需要去運行這個函式,也只需要做這一件事情,換言之,如果我們用 class 的話,那么它的類背后是一個物件,而這個物件又有很多的生命周期函式等等,這就顯得沒有那么純粹了,因此,我們定義無狀態組件這樣的方式,來讓組件更加地純正,

🎩八、Redux發起異步請求

1、Redux中發送異步請求資料

往往在實際的專案中,我們總是需要去和后端請求介面資料并發送 AJAX 請求,那想要在 react 中請求到后端介面資料,該怎么處理呢?

首先我們在 TodoList.js 下面,來請求資料,具體代碼如下:

import { getInputChangeAction, getAddItemAction, getDeleteItemAction, initListAction } from './store/actionCreators';

class TodoList extends Component {
	componentDidMount() {
        axios.get('./list.json').then((res) => {
            const data = res.data;
            const action = initListAction(data);
            store.dispatch(action);
        })
    }
}

接著,修改 actionTypes.js 代碼,具體如下:

export const CHANGE_INPUT_VALUE = 'change_input_value';
export const ADD_TODO_ITEM = 'add_todo_item';
export const DELETE_TODO_ITEM = 'delete_todo_item';
export const INIT_LIST_ACTION = 'init_list_action';

繼續,我們在 actionCreators.js 中對封裝 action具體代碼如下:

import { CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM, INIT_LIST_ACTION } from "./actionTypes";

export const getInputChangeAction = (value) => ({
  type: CHANGE_INPUT_VALUE,
  value: value
});

export const getAddItemAction = (value) => ({
  type: ADD_TODO_ITEM
});

export const getDeleteItemAction = (index) => ({
  type: DELETE_TODO_ITEM,
  index: index
});

export const initListAction = (data) => ({
  type: INIT_LIST_ACTION,
  data: data
})

最后,修改 reducer.js 代碼,具體代碼如下:

import {
  CHANGE_INPUT_VALUE,
  ADD_TODO_ITEM,
  DELETE_TODO_ITEM,
  INIT_LIST_ACTION
} from './actionTypes';

const defaultStore = {
  inputValue: '123',
  list: [1, 2, 3]
};

// reducer 可以接收state,但是絕不能修改state
const reducer = (state = defaultStore, action) => {
  if (action.type === CHANGE_INPUT_VALUE) {
    const newState = JSON.parse(JSON.stringify(state));
    newState.inputValue = action.value;
    return newState;
  }
  if (action.type === INIT_LIST_ACTION) {
    const newState = JSON.parse(JSON.stringify(state));
    newState.list = action.data;
    return newState;
  }
  if (action.type === ADD_TODO_ITEM) {
    const newState = JSON.parse(JSON.stringify(state));
    newState.list.push(newState.inputValue);
    newState.inputValue = '';
    console.log(newState);
    return newState;
  }
  if (action.type === DELETE_TODO_ITEM) {
    const newState = JSON.parse(JSON.stringify(state));
    newState.list.splice(action.index, 1);
    return newState;
  }
  return state;
}
export default reducer;

由此,我們就實作了通過 axios 的方式來發布 AJAX 請求,請讓其獲取到資料,

2、Redux-thunk中間件

(1)解決什么問題

在上面的例子中,我們成功地對介面的資料發起了請求,上面這種情況是屬于比較簡單的例子,但是往往在實際場景中我們遇到的,都是比較復雜的例子,

因此,我們希望的是,當遇到異步請求或者是有著非常復雜邏輯的時候,把它移出到其他檔案下進行管理,

那這個時候就需要用到 Redux-thunk 中間件來進行問題解決,接下來我們來看下 Redux-thunk 中間件如何使用?

(2)如何使用

第一步: 安裝 redux-thunk具體命令如下:

npm i redux-thunk -D

第二步: 引入 redux-thunk ,往往我們在實際除錯中,都會受用 redux-devtools 去對專案的 store 進行除錯,但如果我們既要引入 redux-devtools ,又要引入 redux-thunk 中間件,該怎么處理呢?在 store|index.js 檔案中進行處理,具體代碼如下:

// compose函式來自于redux中
import { createStore, applyMiddleware, compose } from "redux";
import reducer from './reducer';
import thunk from 'redux-thunk';

const composeEnhancers = 
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?   
    window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;

const enhancer = composeEnhancers(
  applyMiddleware(thunk)
);

const store = createStore(
  reducer,
  enhancer
);

export default store;

通過這種形式的編碼,使得我們的 store 既支持 windows 下的 devtools ,也就是可以去除錯 store又可以成功的引入 redux-thunk

第三步: 將異步邏輯進行抽離,先來修改 TodoList.js 的代碼,具體如下:

import { getTodoList, getInputChangeAction, getAddItemAction, getDeleteItemAction } from './store/actionCreators';

class TodoList extends Component {
	componentDidMount() {
        // 這里的action是一個函式
        const action = getTodoList();
        // 只有用了thunk,action才能是用函式的形式去進行傳遞
        store.dispatch(action);
    }
}

接著,修改 actionCreators.js 的代碼,具體代碼如下:

// getTodoList 是一個函式
// 以這種形式生成的函式,可以直接接收dispatch方法
export const getTodoList = () => {
  return (dispatch) => {
    axios.get('./list.json').then((res) => {
      const data = res.data;
      // 這里的 action 是一個物件
      const action = initListAction(data);
      dispatch(action);
    })
  }
}

下面,我們來解釋下上面這兩段代碼,具體如下:

配置好 redux-thunk 的環境之后,它使得我們可以在 action 里面,寫異步的代碼了!為什么這么說呢?

  • 以前我們在創建 action 時,只能是一個JS物件,而現在,當使用了 redux-thunk 之后,即使 getTodoList() 回傳的不是一個物件而是一個函式,也可以通過 store.dispatch() 的方式,把函式發送給到 store 了,
  • 那為什么能夠把函式給發送出去呢?就是因為用了 redux-thunk

繼續,我們要談論具體的實作步驟👇:

  • 首先讓 TodoList.js 中的 store ,去執行 action 函式,而這個 action 函式,來自于 actionCreators.js 中的 getTodoList()
  • 對于 getTodoList() 來說,它要做的事情是去請求json的資料獲取json的資料
  • 而獲取好了資料之后,接下來,要改變 store 里面的資料,那么要先去創建一個 action ,這個 action 用來提供給 store.dispatch() 進行呼叫,但是呢, store.dispatch() 要怎么去獲取呢?我們所回傳的那個函式中,就會自動地接收到 store.dispatch() 方法,所以,只要通過 dispatch(action) ,將 action 給派發出去就可以了,
  • 也就是說, redux-thunk 使得我們去創建 action 或者支持 action 時,是一個函式的形式,

(3)為什么要使用 redux-thunk ?

看完上面的解釋之后,相信大家也就知道 redux-thunk 的奇妙之處了,那為什么要使用 redux-thunk 呢?👇

如果把異步函式放在組件的生命周期中來使用的話,那么這個組件的邏輯就會變得越來越復雜,組件的內容也會變得越來越多,因此,我們通常就會把這種復雜的異步邏輯給拆分出去進行單獨管理,那么現在,我們就借助 redux-thunk 中間件,把異步邏輯給拆分到 actionCreators.js 去進行單獨管理,由此,使得代碼更加規范和統一,

(4)什么是 Redux-thunk 中間件?

在有了上面內容的鋪墊之后,接下來,我們回傳到中間件的源頭,來談談 Redux-thunk 中間件的原理,

所謂中間件,肯定就是說是誰和誰的中間,我們先來看一張圖:

Redux Data Flow

Redux 中間件的這個中間,指的是 actionstore 之間,

之前我們說過,在 redux 中, action 只能是一個物件,就因為它是物件,因此直接把它派發給 store ,現在,當我們使用了 redux-thunk 之后, action 就可以是函式了,那為什么可以是函式呢?

看上面的圖中不難發現, action 通過 dispatch 的方法,將資料遞交給了 store ,且 actionstore 之間,是一個 dispatch 方法,那我們說的中間件 middleware ,實際上就是對 dispatch 方法的封裝和升級

對于最原始的 dispatch 方法來說,它會接收到一個 JS 物件并將其傳遞給 store

但如果我們傳遞的是一個 函式 的話,那么這個 dispatch 就升級了, dispatch 不會直接把函式傳遞給 store ,它會通過 redux-thunk 中間件的方式,先執行對應的函式,等執行到需要呼叫 store 的時候,再去呼叫 store

💼九、Redux的其他中間件

1、Redux-logger

redux 的中間件非常的多,比如 redux-logger 可以記錄 action 每一次派發的日志,那它怎么記錄呢?

它在每一次呼叫 action 的時候,會通過 dispatch 方法把 action 傳遞給 store ,之后呢,我們可以對 dispatch 做一個升級,讓 dispatch 不僅把 action 傳遞給 store ,而且在每一次傳遞之前,我們還通過 console.log 的方式將其列印出來,這樣的話,我們就寫了一個 redux-logger 的中間件, 它可以在我們派發 action 的時候,把 action 列印在我們的控制臺里面,

2、Redux-saga

(1)Redux-saga是什么

在現如今的專案中,用的比較火的中間件不僅有 redux-thunkredux-logger ,還有 reudx-saga 的使用范圍也非常的廣,

reudx-saga 也是解決 react異步問題的一個中間件,不同于 redux-thunk 的是, redux-thunk 采用的是把異步操作放到 action 里面去操作,而 redux-saga 采用的設計思想是單獨地把異步邏輯拆分出來,放到另一個檔案中去進行管理,那 redux-saga 這個中間件該如何使用呢?

(2)Redux-saga如何使用

我們把上面的 TodoList 組件進行升級改造,首先是 store|index.js 檔案,具體代碼如下:

import { createStore, applyMiddleware, compose } from "redux";
import reducer from './reducer';
import createSagaMiddleware from 'redux-saga';
import todoSagas from './sagas';

const sagaMiddleware = createSagaMiddleware()
const composeEnhancers = 
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?   
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
const enhancer = composeEnhancers(sagaMiddleware(sagaMiddleware));

const store = createStore(reducer, enhancer);
sagaMiddleware.run(todoSagas);

export default store;

在這個檔案當中,主要是要把基礎配置做好,那這里主要有幾個要注意的點是:

  • 引入 createSagaMiddleware
  • 之后是使用 const sagaMiddleware = createSagaMiddleware() 將其進行引入;
  • 使用 apllyMiddleware 去使用這個中間件;
  • 使用完中間件之后,我們又創建了 saga.js

接下來我們在 store 檔案夾下創建 saga.js具體代碼如下:

import { takeEvery, put } from 'redux-saga/effects';
import { initListAction } from './actionCreators';
import { GET_INIT_LIST } from './actionTypes';
import axios from 'axios';

function getInitList() {
  try {
    const res = yield axios.get('./list.json');
    const action = initListAction(res.data);
    yield put(action);
  } catch (e) {
    console.log('list.json網路請求失敗');
  }
}

function* mySaga() {
  // 通過takeEvery去捕獲到每一次派發下來的action
  yield takeEvery(GET_INIT_LIST, getInitList);
}

export default mySaga;

對于 saga.js 來說,有幾個要注意的點是:

  • saga.js 里面,一定要匯出一個 generator 函式,在這個函式里面,我們寫了一些邏輯,邏輯是,當我們接收到的 action 型別是 GET_INIT_LIST 時,那么我們就會去執行 getInitList 這個方法,
  • getInitList() 方法是一個函式,它將會去幫我們取資料,取完資料之后,再將這個資料創建出來一個新的 action ,并將這個 action 通過 yield put(action) 的方式,派發給 store

下面我們來看 actionTypes.js 中的內容,具體代碼如下:

// CHANGE_INPUT_VALUE、ADD_TODO_ITEM、DELETE_TODO_ITEM、INIT_LIST_ACTION
export const GET_INIT_LIST = 'get_init_list';

接著,我們來到 TodoList.js具體代碼如下:

import { getInputChangeAction, getAddItemAction, getDeleteItemAction, getInitList } from './store/actionCreators';

class TodoList extends Component {
    
    // 此處省略n多內容
    
    componentDidMount() {
        const action = getInitList();
        store.dispatch(action);
    }
}

export default TodoList;

最后是 store|actionCreators.js ,具體代碼如下:

import { GET_INIT_LIST, CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM, INIT_LIST_ACTION } from "./actionTypes";

// 此處省略n多內容

export const getInitList = () => ({
  type: GET_INIT_LIST
});

TodoList.js 中,我們創建了一個 action ,并將這個 action 派發給 store

🛵十、React-Redux

1、React-Redux是什么

在學習了 react 之后,緊接著,我們學習了 redux ,那如果把它們倆結合起來, react-redux 是什么呢?

實際上,它是一個第三方模塊,它使得我們在 react 中更加方便地使用 redux

2、React-Redux的使用

(1)安裝React-Redux

同樣地,我們以 TodoList 組件為例,來看下 react-redux 的使用,首先新創建一個 react 專案,同時安裝 react-redux具體命令如下:

npm install react-redux

(2)專案目錄

下面先來看專案目錄,具體如下圖:

在這里插入圖片描述

(3)核心內容

第一步,將 TodoList 組件掛載到頁面上,src|index.js 檔案下的內容如下:

import React from 'react';
import ReactDOM from 'react-dom';
import TodoList from './TodoList';
import { Provider } from 'react-redux';
import store from './store';


const App = (
  // 表示Provider里面所有的組件,都有能力獲取到store
  <Provider store={store}>
    <TodoList />
  </Provider>
)

ReactDOM.render(App, document.getElementById('root'));

Providerreact 提供的第一個核心 API ,它旨在表明, Provider 里面所有的組件,都有能力獲取到 store


第二步,撰寫 src|TodoList.js 的內容,具體代碼如下:

import React from 'react';
import { connect } from 'react-redux';

const TodoList = (props) => {
  const { inputValue, list, changeInputValue, handleClick, handleDelete } = props;
  return (
    <div>
      <div>
        <input value={inputValue} onChange={ changeInputValue }/>
        <button onClick={ handleClick }>提交</button>
      </div>
      <ul>
        {
          list.map((item, index) => {
            return <li onClick={handleDelete} key={index}>{ item }</li>
          })
        }
      </ul>
    </div>
  )
}

const mapStateToProps = (state) => {
  return {
    inputValue: state.inputValue,
    list: state.list
  }
}

// store, dispatch, props
const mapDispatchToProps = (dispatch) => {
  return {
    changeInputValue(e) {
      const action = {
        type: 'change_input_value',
        value: e.target.value
      };
      // console.log(action.value)
      dispatch(action);
    },
    handleClick() {
      const action = {
        type: 'add_item'
      }
      dispatch(action);
    },
    handleDelete() {

    }
  }
}

// 讓我們的TodoList和store做連接
// TodoList是一個UI組件,connect把這個UI組件和前邊的業務邏輯相結合,可以把前面括號的內容稱為是容器組件
export default connect(mapStateToProps, mapDispatchToProps)(TodoList);

在上面的代碼中,我們要注意的是 react-redux 中的 connect

connect 表示的是連接,那么是誰和誰做連接呢TodoListstore 做連接,它們倆做連接需要一個映射關系,這個映射關系就在 mapStateToProps 里面,

mapStateToProps 中, state 指的是 store 里面的資料,那 store 里面的資料,就把它映射到 props 里面,之后我們就可以通過 this.props.xxx 的方式,去獲取到 store 里面的資料,


第三步,創建 reducer ,在 src|store|reducer.js 下進行撰寫,具體代碼如下:

const defaultState = {
  inputValue: '',
  list: []
}

export default (state = defaultState, action) => {
  if (action.type === 'change_input_value') {
    const newState = JSON.parse(JSON.stringify(state));
    newState.inputValue = action.value;
    return newState;
  }
  if (action.type === 'add_item') {
    const newState = JSON.parse(JSON.stringify(state));
    newState.list.push(newState.inputValue);
    newState.inputValue = '';
    return newState;
  }
  return state;
}

store 中的資料給放到 reducer 當中去進行記錄,


第四步,將 reducer 傳給 store ,在 src|store|index.js 下進行撰寫,具體代碼如下:

import { createStore } from 'redux';
import reducer from './reducer';

const store = createStore(reducer);

export default store;

我們將 reducer 中存放的內容進行深拷貝,并把它傳回給 store ,這樣,就形成了一個資料傳遞的倍訓,


最后,我們來看一下瀏覽器顯示的效果:

React-Redux

相比于使用中間件來說, React-Redux 的使用更加地直觀簡潔,在實際專案中,不管是 redux 中間件,還是 react-redux ,都值得拿來做狀態管理,

那么要注意的是,redux 中間件和 react-redux 之間,各自在使用程序中不同的點,區分好即可,至于在專案中使用哪一種型別,就依據當下的專案場景去決定就好啦!

🚦十一、結束語

在上面的文章中,我們講解了 Redux 設計和使用的三項原則,同時,也講解了 Redux 中的一些核心 API ,除此之外呢,我們還學習了 redux 的中間件, redux-thunkredux-saga ,同時,還學習了另外一個做狀態管理的內容, react-redux

到這里,關于 redux 的內容就介紹完畢啦!不知道大家是否對 redux 又有了新的了解呢?

🐣彩蛋 One More Thing

(:往期推薦

👉初探react,用react實作一個todoList功能

👉react只停留在表層?五大知識點帶你梳理進階知識

(:番外篇

  • 關注公眾號星期一研究室,第一時間關注優質文章,更有 「offer來了」面試專欄 待你解鎖~
  • 如果您覺得這篇文章有幫助到您的的話不妨點贊支持一下喲~~😉
  • 以上就是本文的全部內容!我們下期見!👋👋👋

轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/335167.html

標籤:其他

上一篇:Java專案:婚紗影樓攝影預約網站設計和實作(javaweb+SSM+springboot)

下一篇:Java專案:前后端分離疫情防疫平臺設計和實作(java+springmvc+VUE+node.js+mybatis+mysql+springboot+redis+jsp)

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • vue移動端上拉加載

    可能做得過于簡單或者比較low,請各位大佬留情,一起探討技術 ......

    uj5u.com 2020-09-10 04:38:07 more
  • 優美網站首頁,頂部多層導航

    一個個人用的瀏覽器首頁,可以把一下常用的網站放在這里,平常打開會比較方便。 第一步,HTML代碼 <script src=https://www.cnblogs.com/szharf/p/"js/jquery-3.4.1.min.js"></script> <div id="navigate"> <ul> <li class="labels labels_1"> ......

    uj5u.com 2020-09-10 04:38:47 more
  • 頁面為要加<!DOCTYPE html>

    最近因為寫一個js函式,需要用到$(window).height(); 由于手寫demo的時候,過于自信,其實對前端方面的認識也不夠體系,用文本檔案直接敲出來的html代碼,第一行沒有加上<!DOCTYPE html> 導致了$(window).height();的結果直接是整個document的高 ......

    uj5u.com 2020-09-10 04:38:52 more
  • WordPress網站程式手動升級要做好資料備份

    WordPress博客網站程式在進行升級前,必須要做好網站資料的備份,這個問題良家佐言是遇見過的;在剛開始接觸WordPress博客程式的時候,因為升級問題和博客網站的修改的一些嘗試,良家佐言是吃盡了苦頭。因為購買的是西部數碼的空間和域名,每當佐言把自己的WordPress博客網站搞到一塌糊涂的時候 ......

    uj5u.com 2020-09-10 04:39:30 more
  • WordPress程式不能升級為5.4.2版本的原因

    WordPress是一款個人博客系統,受到英文博客愛好者和中文博客愛好者的追捧,并逐步演化成一款內容管理系統軟體;它是使用PHP語言和MySQL資料庫開發的,用戶可以在支持PHP和MySQL資料庫的服務器上使用自己的博客。每一次WordPress程式的更新,就會牽動無數WordPress愛好者的心, ......

    uj5u.com 2020-09-10 04:39:49 more
  • 使用CSS3的偽元素進行首字母下沉和首行改變樣式

    網頁中常見的一種效果,首字改變樣式或者首行改變樣式,效果如下圖。 代碼: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, ......

    uj5u.com 2020-09-10 04:40:09 more
  • 關于a標簽的講解

    什么是a標簽? <a> 標簽定義超鏈接,用于從一個頁面鏈接到另一個頁面。 <a> 元素最重要的屬性是 href 屬性,它指定鏈接的目標。 a標簽的語法格式:<a href=https://www.cnblogs.com/summerxbc/p/"指定要跳轉的目標界面的鏈接">需要展示給用戶看見的內容</a> a標簽 在所有瀏覽器中,鏈接的默認外觀如下: 未被訪問的鏈接帶 ......

    uj5u.com 2020-09-10 04:40:11 more
  • 前端輪播圖

    在需要輪播的頁面是引入swiper.min.js和swiper.min.css swiper.min.js地址: 鏈接:https://pan.baidu.com/s/15Uh516YHa4CV3X-RyjEIWw 提取碼:4aks swiper.min.css地址 鏈接:https://pan.b ......

    uj5u.com 2020-09-10 04:40:13 more
  • 如何設定html中的背景圖片(全屏顯示,且不拉伸)

    1 <style>2 body{background-image:url(https://uploadbeta.com/api/pictures/random/?key=BingEverydayWallpaperPicture); 3 background-size:cover;background ......

    uj5u.com 2020-09-10 04:40:16 more
  • Java學習——HTML詳解(上)

    HTML詳解 初識HTML Hyper Text Markup Language(超文本標記語言) 1 <!--DOCTYPE:告訴瀏覽器我們要使用什么規范--> 2 <!DOCTYPE html> 3 <html lang="en"> 4 <head> 5 <!--meta 描述性的標簽,描述一些 ......

    uj5u.com 2020-09-10 04:40:33 more
最新发布
  • 我的第一個NPM包:panghu-planebattle-esm(胖虎飛機大戰)使用說明

    好家伙,我的包終于開發完啦 歡迎使用胖虎的飛機大戰包!! 為你的主頁添加色彩 這是一個有趣的網頁小游戲包,使用canvas和js開發 使用ES6模塊化開發 效果圖如下: (覺得圖片太sb的可以自己改) 代碼已開源!! Git: https://gitee.com/tang-and-han-dynas ......

    uj5u.com 2023-04-20 07:59:23 more
  • 生產事故-走近科學之消失的JWT

    入職多年,面對生產環境,盡管都是小心翼翼,慎之又慎,還是難免捅出簍子。輕則滿頭大汗,面紅耳赤。重則系統停擺,損失資金。每一個生產事故的背后,都是寶貴的經驗和教訓,都是專案成員的血淚史。為了更好地防范和遏制今后的各類事故,特開此專題,長期更新和記錄大大小小的各類事故。有些是親身經歷,有些是經人耳傳口授 ......

    uj5u.com 2023-04-18 07:55:04 more
  • 記錄--Canvas實作打飛字游戲

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 打開游戲界面,看到一個畫面簡潔、卻又富有挑戰性的游戲。螢屏上,有一個白色的矩形框,里面不斷下落著各種單詞,而我需要迅速地輸入這些單詞。如果我輸入的單詞與螢屏上的單詞匹配,那么我就可以獲得得分;如果我輸入的單詞錯誤或者時間過長,那么我就會輸 ......

    uj5u.com 2023-04-04 08:35:30 more
  • 了解 HTTP 看這一篇就夠

    在學習網路之前,了解它的歷史能夠幫助我們明白為何它會發展為如今這個樣子,引發探究網路的興趣。下面的這張圖片就展示了“互聯網”誕生至今的發展歷程。 ......

    uj5u.com 2023-03-16 11:00:15 more
  • 藍牙-低功耗中心設備

    //11.開啟藍牙配接器 openBluetoothAdapter //21.開始搜索藍牙設備 startBluetoothDevicesDiscovery //31.開啟監聽搜索藍牙設備 onBluetoothDeviceFound //30.停止監聽搜索藍牙設備 offBluetoothDevi ......

    uj5u.com 2023-03-15 09:06:45 more
  • canvas畫板(滑鼠和觸摸)

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>canves</title> <style> #canvas { cursor:url(../images/pen.png),crosshair; } #canvasdiv{ bo ......

    uj5u.com 2023-02-15 08:56:31 more
  • 手機端H5 實作自定義拍照界面

    手機端 H5 實作自定義拍照界面也可以使用 MediaDevices API 和 <video> 標簽來實作,和在桌面端做法基本一致。 首先,使用 MediaDevices.getUserMedia() 方法獲取攝像頭媒體流,并將其傳遞給 <video> 標簽進行渲染。 接著,使用 HTML 的 < ......

    uj5u.com 2023-01-12 07:58:22 more
  • 記錄--短視頻滑動播放在 H5 下的實作

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 短視頻已經無數不在了,但是主體還是使用 app 來承載的。本文講述 H5 如何實作 app 的視頻滑動體驗。 無聲勝有聲,一圖頂百辯,且看下圖: 網址鏈接(需在微信或者手Q中瀏覽) 從上圖可以看到,我們主要實作的功能也是本文要講解的有: ......

    uj5u.com 2023-01-04 07:29:05 more
  • 一文讀懂 HTTP/1 HTTP/2 HTTP/3

    從 1989 年萬維網(www)誕生,HTTP(HyperText Transfer Protocol)經歷了眾多版本迭代,WebSocket 也在期間萌芽。1991 年 HTTP0.9 被發明。1996 年出現了 HTTP1.0。2015 年 HTTP2 正式發布。2020 年 HTTP3 或能正... ......

    uj5u.com 2022-12-24 06:56:02 more
  • 【HTML基礎篇002】HTML之form表單超詳解

    ??一、form表單是什么

    ??二、form表單的屬性

    ??三、input中的各種Type屬性值

    ??四、標簽 ......

    uj5u.com 2022-12-18 07:17:06 more