前端(十):使用redux管理數據

来源:https://www.cnblogs.com/kuaizifeng/archive/2018/12/22/10161452.html
-Advertisement-
Play Games

react本身能夠完成動態數據的監聽和更新,如果不是必要可以不適用redux。 安裝redux: cnpm install redux --save,或者yarn add redux。 一、react基本用法 redux是獨立的用於狀態管理的第三方包,它建立狀態機來對單項數據進行管理。 上圖是個人粗 ...


  react本身能夠完成動態數據的監聽和更新,如果不是必要可以不適用redux。

  安裝redux: cnpm install redux --save,或者yarn add redux。

一、react基本用法

  redux是獨立的用於狀態管理的第三方包,它建立狀態機來對單項數據進行管理。

  上圖是個人粗淺的理解。用代碼驗證一下:

// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from "redux";

function reducer(state={name: "monkey", age: 5000}, action){
switch (action.type){
case "add":
state.age ++;
return state;
case "dec":
if (state.age <= 4995){
state.name = "small monkey";
}
state.age --;
return state;
default:
return state;
}
}
const store = createStore(reducer);
const add = {type: "add"}, dec={type: "dec"};


class App extends React.Component{
render (){
const style={
display: "inline-block",
width: "150px",
height: "40px",
backgroundColor: "rgb(173, 173, 173)",
color: "white",
marginRight: "20px"
};
const store = this.props.store;
// console.log(store.getState());
const obj = store.getState();
// console.log(obj);
return (
<div>
<h2>this { obj.name } is { obj.age } years old.</h2>
<button style={style} onClick={()=>store.dispatch(this.props.add)}>增加一歲</button>
<button style={style} onClick={()=>store.dispatch(this.props.dec)}>減少一歲</button>
</div>
)
}
}
function render() {
ReactDOM.render(<App store={store} add={ add } dec={ dec } />, document.getElementById('root'));
}
render();
store.subscribe(render);

  因為action必須是個對象,所以只能寫成add = {type: "add"}的形式,而不能直接寫參數"add"。同樣地,在reducer中寫switch時將action.type作為參數。

  action和state一一對應,要使用action必須要在reducer里聲明。

  redux沒有用state來實現動態數據更新,而是通過props來傳遞數據,因此在組件內部只能通過props獲取store,以及store.getState()獲取state。

  redux將ReactDOM.render進行了一次封裝來設置監聽。

  redux對數據和組件進行瞭解耦,因而可以進行文件拆分。

  把action寫成函數的形式:

// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from "redux";

const Add = "add", Dec="dec";
function reducer(state={name: "monkey", age: 5000}, action){
switch (action.type){
case Add:
state.age ++;
if (state.age > 5005){
state.name = "old monkey";
}
return state;
case Dec:
if (state.age <= 4995){
state.name = "small monkey";
}
state.age --;
return state;
default:
return state;
}
}
const store = createStore(reducer);
class App extends React.Component{
render (){
const style={
display: "inline-block",
width: "150px",
height: "40px",
backgroundColor: "rgb(173, 173, 173)",
color: "white",
marginRight: "20px"
};
const store = this.props.store;
const state = store.getState();
return (
<div>
<h2>this { state.name } is { state.age } years old.</h2>
<button style={style} onClick={()=>store.dispatch(add())}>增加一歲</button>
<button style={style} onClick={()=>store.dispatch(dec())}>減少一歲</button>
</div>
)
}
}
function render() {
ReactDOM.render(<App store={store} add={ add } dec={ dec } />, document.getElementById('root'));
}
render();
store.subscribe(render);

   或者也可以寫成原始的:

// reducer.js
const defaultState = {
    inputValue: '',
    list: [1, 2, 3]
};

// reducer可以接受state,但是無法修改state,所以只能拷貝修改
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);
        return newState;
    }
    if(action.type === 'delete_item'){
        const newState = JSON.parse(JSON.stringify(state));
        newState.list.splice(action.value, 1);
        return newState;
    }
    return state;
}
// store.js
import {createStore} from 'redux';
import reducer from './reducer';
const store = createStore(
    reducer,
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
    );

export default store;
// ReduxDemo.js
import React, {Fragment} from 'react';
import 'antd/dist/antd.css';
import { Input, Button, Row, Col, List } from 'antd';
import store from './store';

class ReduxDemo extends React.Component{

    constructor(props){
        super(props);
        this.state = store.getState();
        this.handleBtnClick = this.handleBtnClick.bind(this);
        this.handleInpChange = this.handleInpChange.bind(this);
        this.handleStoreChange = this.handleStoreChange.bind(this);
        this.handleItemDelete = this.handleItemDelete.bind(this);
        // 使用subscribe監聽函數時一定要事先綁定到this上
        store.subscribe(this.handleStoreChange);
    }

    render(){
        return (
            <Fragment>
                <Row gutter={24} size="large">
                    <Col span={12} offset={4} style={{padding: "0px"}}>
                        <Input defaultValue='todo info' style={{height:"50px"}} value={this.state.inputValue}
                               onChange={this.handleInpChange}
                        />
                    </Col>
                    <Col span={2} style={{padding:"0px"}}>
                        <Button
                            type='primary'
                            style={{width:"100%", height:"50px"}}
                            onClick={this.handleBtnClick}
                        >submit</Button>
                    </Col>
                </Row>
                <List
                    bordered={1}
                    dataSource={this.state.list}
                    renderItem={
                        (item, index) => (
                            <List.Item column={12}
                                       onClick={this.handleItemDelete.bind(this,index)}
                            >{item}</List.Item>
                        )
                    }
                />
            </Fragment>
        )
    }
    handleBtnClick(){
        const action = {
            type: 'add_item'
        };
        store.dispatch(action);
    }

    handleInpChange(e) {
        const action = {
            type: 'change_input_value',
            value: e.target.value
        };
        store.dispatch(action);
    }
    handleStoreChange(){
        this.setState(()=>store.getState());
    };
    handleItemDelete(index){
        const action = {
            type: 'delete_item',
            value: index
        };
        store.dispatch(action);     // dispatch被監聽函數調用,並將action提交給store,store將上一次的數據狀態state和本次的action一起交給reducer去執行邏輯處理
    }
}
export default ReduxDemo;

  或者改為:

// actioncreators
export const CHANGE_INPUT_VALUE = 'change_input_value';
export const ADD_ITEM = 'add_item';
export const DELETE_ITEM = 'delete_item';

export const getInpChangeAction = (value)=>({
        type: CHANGE_INPUT_VALUE,
        value
});
export const getAddItemAction = ()=>({
    type: ADD_ITEM,
});
export const getDeleteItemAction = (index)=>({
    type: DELETE_ITEM,
    value: index
});
// reducers
import {CHANGE_INPUT_VALUE, ADD_ITEM, DELETE_ITEM} from './actionCreators'

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

// reducer可以接受state,但是無法修改state,所以只能拷貝修改
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);
        return newState;
    }
    if(action.type === DELETE_ITEM){
        const newState = JSON.parse(JSON.stringify(state));
        newState.list.splice(action.value, 1);
        return newState;
    }
    return state;
}
// store
import {createStore} from 'redux';
import reducer from './reducer';
const store = createStore(
    reducer,
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
    );

export default store;
// ReduxDemo
import React, {Fragment} from 'react';
import 'antd/dist/antd.css';
import { Input, Button, Row, Col, List } from 'antd';
import store from './store';
import {getAddItemAction, getDeleteItemAction, getInpChangeAction } from './actionCreators';

class ReduxDemo extends React.Component{

    constructor(props){
        super(props);
        this.state = store.getState();
        this.handleBtnClick = this.handleBtnClick.bind(this);
        this.handleInpChange = this.handleInpChange.bind(this);
        this.handleStoreChange = this.handleStoreChange.bind(this);
        this.handleItemDelete = this.handleItemDelete.bind(this);
        // 使用subscribe監聽函數時一定要事先綁定到this上
        store.subscribe(this.handleStoreChange);
    }

    render(){
        return (
            <Fragment>
                <Row gutter={24} size="large">
                    <Col span={12} offset={4} style={{padding: "0px"}}>
                        <Input defaultValue='todo info' style={{height:"50px"}} value={this.state.inputValue}
                               onChange={this.handleInpChange}
                        />
                    </Col>
                    <Col span={2} style={{padding:"0px"}}>
                        <Button
                            type='primary'
                            style={{width:"100%", height:"50px"}}
                            onClick={this.handleBtnClick}
                        >submit</Button>
                    </Col>
                </Row>
                <List
                    bordered={1}
                    dataSource={this.state.list}
                    renderItem={
                        (item, index) => (
                            <List.Item column={12}
                                       onClick={this.handleItemDelete.bind(this,index)}
                            >{item}</List.Item>
                        )
                    }
                />

            </Fragment>
        )
    }
    handleBtnClick(){
        const action = getAddItemAction();
        store.dispatch(action);
    }
    handleInpChange(e) {
        const action = getInpChangeAction(e.target.value);
        store.dispatch(action);
    }
    handleStoreChange(){
        this.setState(()=>store.getState());
    };
    handleItemDelete(index){
        const action = getDeleteItemAction(index);
        store.dispatch(action);
    }
}
export default ReduxDemo;

二、UI組件與容器組件拆解

  容器組件負責邏輯處理,UI組件只負責頁面顯示。基於這樣的邏輯,可以將上述的ReduxDemo進行拆解。

// 容器組件
import React from 'react';
import store from './store';
import ReduxDemoUI from './ReduxDemoUI';
import {getAddItemAction, getDeleteItemAction, getInpChangeAction } from './actionCreators';

class ReduxDemo extends React.Component{
    constructor(props){
        super(props);
        this.state = store.getState();
        this.handleBtnClick = this.handleBtnClick.bind(this);
        this.handleInpChange = this.handleInpChange.bind(this);
        this.handleStoreChange = this.handleStoreChange.bind(this);
        this.handleItemDelete = this.handleItemDelete.bind(this);
        // 使用subscribe監聽函數時一定要事先綁定到this上
        store.subscribe(this.handleStoreChange);
    }

    render(){
        return (
            <ReduxDemoUI
                inputValue={this.state.inputValue}
                list={this.state.list}
                handleBtnClick={this.handleBtnClick}
                handleInpChange={this.handleInpChange}
                handleStoreChange={this.handleStoreChange}
                handleItemDelete={this.handleItemDelete}
            />)
    }
    handleBtnClick(){
        const action = getAddItemAction();
        store.dispatch(action);
    }
    handleInpChange(e) {
        const action = getInpChangeAction(e.target.value);
        store.dispatch(action);
    }
    handleStoreChange(){
        this.setState(()=>store.getState());
    };
    handleItemDelete(index){
        const action = getDeleteItemAction(index);
        store.dispatch(action);
    }
}
export default ReduxDemo;
// UI組件
import React, {Fragment} from 'react';
import 'antd/dist/antd.css';
import { Input, Button, Row, Col, List } from 'antd';

class ReduxDemoUI extends React.Component{
    render(){
        return (
            <Fragment>
                <Row gutter={24} size="large">
                    <Col span={12} offset={4} style={{padding: "0px"}}>
                        <Input defaultValue='todo info' style={{height:"50px"}} value={this.props.inputValue}
                               onChange={this.props.handleInpChange}
                        />
                    </Col>
                    <Col span={2} style={{padding:"0px"}}>
                        <Button
                            type='primary'
                            style={{width:"100%", height:"50px"}}
                            onClick={this.props.handleBtnClick}
                        >submit</Button>
                    </Col>
                </Row>
                <List
                    bordered={1}
                    dataSource={this.props.list}
                    renderItem={
                        (item, index) => (
                            <List.Item column={12}
                                       onClick={(index) => {this.props.handleItemDelete(index)}}
                            >{item}</List.Item>
                        )
                    }
                />

            </Fragment>
        )
    }
}
export default ReduxDemoUI;

   當一個組件只有render函數時,可以改寫成無狀態組件,無狀態組件只是一個函數,因此性能要比UI組件高。

// 將ReduxDemoUI改寫
import React, {Fragment} from 'react';
import 'antd/dist/antd.css';
import { Input, Button, Row, Col, List } from 'antd';

export const ReduxDemoUI = (props) => {
    return (<Fragment>
        <Row gutter={24} size="large">
            <Col span={12} offset={4} style={{padding: "0px"}}>
                <Input defaultValue='todo info' style={{height: "50px"}} value={props.inputValue}
                       onChange={props.handleInpChange}
                />
            </Col>
            <Col span={2} style={{padding: "0px"}}>
                <Button
                    type='primary'
                    style={{width: "100%", height: "50px"}}
                    onClick={props.handleBtnClick}
                >submit</Button>
            </Col>
        </Row>
        <List
            bordered={1}
            dataSource={props.list}
            renderItem={
                (item, index) => (
                    <List.Item column={12}
                               onClick={() => {
                                   props.handleItemDelete(index)
                               }}
                    >{item}</List.Item>
                )
            }
        />
    </Fragment>)};
// ReduxDemo只修改導入方式
// import ReduxDemoUI from './ReduxDemoUI';
import {ReduxDemoUI} from './ReduxDemoUI';

三、請求數據渲染

  利用redux+axios發送數據,獲取數據並將數據渲染。

// actionCreators
export const CHANGE_INPUT_VALUE = 'change_input_value';
export const ADD_ITEM = 'add_item';
export const DELETE_ITEM = 'delete_item';

export const AXIO_LIST = 'axios_list'; // 添加狀態

export const getInpChangeAction = (value)=>({
        type: CHANGE_INPUT_VALUE,
        value
});
export const getAddItemAction = ()=>({
    type: ADD_ITEM,
});
export const getDeleteItemAction = (index)=>({
    type: DELETE_ITEM,
    value: index
});

// 添加action export const getAxiosListAction
= (list) => ({ type: AXIO_LIST, list });
// reducers
import {CHANGE_INPUT_VALUE, ADD_ITEM, DELETE_ITEM, AXIO_LIST} from './actionCreators'

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);
        return newState;
    }
    if(action.type === DELETE_ITEM){
        const newState = JSON.parse(JSON.stringify(state));
        newState.list.splice(action.value, 1);
        return newState;
    }
  // 添加狀態處理
if(action.type === AXIO_LIST){ const newState = JSON.parse(JSON.stringify(state)); newState.list = action.list; return newState; } return state; }
// ReduxDemo

import React from 'react';
import store from './store';
import axios from 'axios';
import {ReduxDemoUI} from './ReduxDemoUI';
import {getAddItemAction, getDeleteItemAction, getInpChangeAction, getAxiosListAction } from './actionCreators';

class ReduxDemo extends React.Component{
    constructor(props){
        super(props);
        this.state = store.getState();
        this.handleBtnClick = this.handleBtnClick.bind(this);
        this.handleInpChange = this.handleInpChange.bind(this);
        this.handleStoreChange = this.handleStoreChange.bind(this);
        this.handleItemDelete = this.handleItemDelete.bind(this);
        // 使用subscribe監聽函數時一定要事先綁定到this上
        store.subscribe(this.handleStoreChange);
    }

    render(){
        return (
            <ReduxDemoUI
                inputValue={this.state.inputValue}
                list={this.state.list}
                handleBtnClick={this.handleBtnClick}
                handleInpChange={this.handleInpChange}
                handleStoreChange={this.handleStoreChange}
                handleItemDelete={this.handleItemDelete}
            />)
    }
    handleBtnClick(){
        const action = getAddItemAction();
        store.dispatch(action);
    }
    handleInpChange(e) {
        const action = getInpChangeAction(e.target.value);
        store.dispatch(action);
    }
    handleStoreChange(){
        this.setState(()=>store.getState());
    };
    handleItemDelete(index){
        const action = getDeleteItemAction(index);
        store.dispatch(action);
    }
   // 發送請求,獲取list,並交給store,store將action和list交給reducer處理,然後store在constructor中監聽返回
// 然後storec調用store.dispatch將reducer更新後的數據更新到組件的setState中,隨後render方法開始渲染 componentDidMount() { axios({ method:
'get', url: 'http://localhost:8080/ccts/node/test', responseType: 'json' }).then((response) => { // 請求成功時 const action = getAxiosListAction(response.data.list); store.dispatch(action); }).catch((error) => { console.log(error); }); } } export default ReduxDemo;

  store.js和ReduxDemoUI.js保持不變。啟動一個後臺服務,這裡用java起了一個後臺來傳遞list。註意在package.json中設置proxy為"http://localhost:8080"。

@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/node")
public class Service {
    @RequestMapping(value = {"/test"}, method = {RequestMethod.GET})
    @ResponseBody
    public String test(HttpServletRequest request){
        // consumes = "application/json")
        Map<String, ArrayList> map = new HashMap<String, ArrayList>(1);
        ArrayList<String> strings = new ArrayList<String>();
        strings.add("張三");
        strings.add("李四");
        strings.add("王二");
        strings.add("麻子");
        map.put("list", strings);
        return JSON.toJSONString(map);
    }
}

四、redux非同步處理

  當一個父組件中有多個子組件時,如果每一個組件發生狀態變化,store都要重新渲染一遍。使用非同步可以只重新渲染髮生狀態變化的組件。

  安裝redux和thunk的中間件redux-thunk:cnpm install redux-thunk --save。引入兩個函數,並修改createStore如下,其餘代碼保持不變:

import { createStore, applyMiddleware } from "redux";
import thunk from 'redux-thunk';
const store = createStore(reducer, applyMiddleware(thunk));

  當然還可以使用compose在控制台添加調試功能,前提是這需要在瀏覽器中安裝redux插件,百度下載redux-devtools.crx,並直接拖動到谷歌瀏覽器擴展程式頁面(這就是安裝了)。

import { createStore, applyMiddleware, compose } from "redux";
const store = createStore(
reducer,
compose(
applyMiddleware(thunk),
window.devToolsExtension?window.devToolsExtension():f=>f
)
);

  最終代碼如下:

import {createStore, applyMiddleware, compose} from 'redux';
import thunk from  'redux-thunk';
import reducer from './reducer';
const store = createStore(
    reducer,
    compose(
        applyMiddleware(thunk),
        window.devToolsExtension?window.devToolsExtension():f=>f
    )
);
export default store;

  thunk支持非同步請求,它的action可以是一個函數。為了啟用非同步請求,對請求進行改寫。

      thunk中間件指的是store和action之間的中間件。當store.dispatch被執行時,會檢測action是一個對象或是一個函數,當是一個函數時,會自動執行這個函數並將結果再交給store。然後和對象那種處理方式一樣,store將action交給reducer處理。

// actionCreators添加如下方法
export const getAxiosListThunk = () => {
    // 返回一個函數,它可以自動接收一個名為dispatch的方法,處理非同步
    return (dispatch) => {
        axios({
            method: 'get',
            url: 'http://localhost:8080/ccts/node/test',
            responseType: 'json'
        }).then((response) => {
            // 請求成功時
            const action = getAxiosListAction(response.data.list);
            dispatch(action); // 只將store.dispatch()改寫成了dispatch()
        }).catch((error) => {
            console.log(error);
        });
    };
};
// ReduxDemo中修改componnetDidMount
    componentDidMount() {
        const action = getAxiosListThunk();
        store.dispatch(action); // dispatch會自動調用和執行action函數
        // axios({
        //     method: 'get',
        //     url: 'http://localhost:8080/ccts/node/test',
        //     responseType: 'json'
        // }).then((response) => {
        //     // 請求成功時
        //     const action = getAxiosListAction(response.data.list);
        //     store.dispatch(action);
        // }).catch((error) => {
        //     console.log(error);
        // });
    }

五、組件間與狀態機、狀態機與狀態機

  內層的組件調用dispatch,那麼它的父組件一直到最外層組件都需要使用store屬性一層一層地傳遞狀態機。

  狀態機與狀態機之間需要使用redux中的combineReducers合併成一個對象。並由dispatch調用的action來查找其對應的reducer一級返回的state。

  舉個例子:

  在src目錄下新建index.redux.js文件,建立兩個reducer以及各自的action。

// src/index.redux.js
const Add = "add", Dec="dec";
export function reducerOne(state={name: "monkey", age: 5000}, action){
switch (action.type){
case Add:
state.age ++;
if (state.age > 5005){
state.name = "old monkey";
}
return state;
case Dec:
if (state.age <= 4995){
state.name = "small monkey";
}
state.age --;
return state;
default:
return state;
}
}
export function add() {
return {type: Add}
}
export function dec() {
return {type: Dec}
}

export function reducerTwo(state="孫悟空", action){
if(action.type >= 5005){
state = "鬥戰勝佛";
}else if(action.type <= 4995){
state = "小獼猴";
}
return state;
}
export function monkey(number) {
return {type: number}
}

  在src/index.js中寫入如下代碼:

// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { createSto
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.前言 在平時的業務開發中,前端通常需要請求後臺獲取數據,或者NodeJs讀取文件等等一系列的非同步操作,我們通常需要利用非同步操作的結果或者對非同步操作的結果進行處理。通常我們的解決方案是:在非同步操作成功或者失敗的回調函數裡面寫方法,在非同步操作比較簡單的時候這樣寫代碼還是比較好理解的,當業務逐漸趨於復 ...
  • 一、什麼是HTML? HTML:超文本標簽語言 (Hyper Text Markup Language) www萬維網的描述性語言。 XHTML指可擴展超文本標記語言(標識語言)(EXtensible HyperText Markup Language)是一種置標語言,表現方式與超文本標記語言(HT ...
  • 1、先寫結構 a.如果列表沒有時間 結構為:<li><a>新聞內容</a></li> b.如果列表有時間 結構為:<li><a>新聞內容</a><span>時間</span></li> 2、給li添加高度 3、若列表有時間,給a標簽,span標簽添加浮動 4、調整文本樣式(大小,顏色等) 5、以背景 ...
  • 函數式編程 filter的使用 reduce curry let dragon = (name,size,element) = console.log(dragon('fluffykins','tiny','lightling')) //curry let dragon = name = size ...
  • JavaScript 系列博客(五) 前言 本篇博客學習 js 選擇器來控制 css 和 html、使用事件(鉤子函數)來處理事件完成後完成指定功能以及js 事件控制頁面內容。 js 選擇器 在學習 js 選擇器前需要瞭解幾個概念。 節點(一):在文檔(document)中出現的所有內容都是 doc ...
  • 1.同源問題解決 首先,在同一個域下搭建網路功能變數名稱訪問,需要nginx軟體,下載之後修改部分配置 然後再終端下cmd nginx.exe命令,或者打開nginx.exe文件,會運行nginx一閃而過,在後臺運行而且一次是打開兩個的,可以在任務管理器控制結束進程, 接下來,你就可以打開8080介面給同域 ...
  • 本文是 "Rxjs 響應式編程 第一章:響應式" 這篇文章的學習筆記。 示例代碼地址: "【示例代碼】" 更多文章: "【《大史住在大前端》博文集目錄】" [TOC] 一. 劃重點 三句非常重要的話: 從理念上來理解,Rx模式引入了一種新的 “一切皆流” 的編程範式 從設計模式的角度來看, 是 發佈 ...
  • 國內的設計師大都喜歡用px,而國外的網站大都喜歡用em和rem,那麼三者有什麼區別,又各自有什麼優劣呢? PX特點 1. IE無法調整那些使用px作為單位的字體大小; 2. 國外的大部分網站能夠調整的原因在於其使用了em或rem作為字體單位; 3. Firefox能夠調整px和em,rem,但是96 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...