React-Redux

React 實(shí)際上只是 UI 框架,通過 JSX 生成動(dòng)態(tài) dom 渲染 UI母赵,沒有架構(gòu)明垢、沒有模板、沒有設(shè)計(jì)模式市咽、沒有路由痊银、也沒有數(shù)據(jù)管理。所以需要借助其他工具施绎。

redux

npm install redux --save

  1. 什么是 redux ?
    ReduxJavaScript 狀態(tài)容器溯革,提供可預(yù)測(cè)化的狀態(tài)管理」茸恚可以理解為全局?jǐn)?shù)據(jù)狀態(tài)管理工具致稀,用來做組件通信等。

  2. 為什么使用 redux ?
    當(dāng)沒有使用 redux 時(shí)兄弟組件間傳值將很麻煩俱尼,代碼很復(fù)雜冗余抖单。使用 redux 定義全局單一的數(shù)據(jù) Store,可以自定義 Store 里面存放哪些數(shù)據(jù)遇八,整個(gè)數(shù)據(jù)結(jié)構(gòu)也是自己清楚的矛绘。

  3. redux 工作流 ?


    redux 工作流
    • store:推送數(shù)據(jù)的倉(cāng)庫
    • reducer:幫助 store 處理數(shù)據(jù)的方法(初始化、修改刃永、刪除)
    • actions:數(shù)據(jù)更新的指令
    • react 組件(UI):訂閱 store 中的數(shù)據(jù)
  4. redux 用法:

import { createStore } from 'redux'

/*
 * 這是一個(gè) reducer货矮,形式為 (state, action) => state 的純函數(shù)。描述了 action 如何把 state 轉(zhuǎn)變成下一個(gè) state斯够。
 * state 的形式取決于你囚玫,可以是基本類型、數(shù)組读规、對(duì)象抓督、甚至是 Immutable.js 生成的數(shù)據(jù)結(jié)構(gòu)。
 * 當(dāng) state 變化時(shí)需要返回全新的對(duì)象束亏,而不是修改傳入的參數(shù)铃在。
 */
function counter(state = 0, action) {
  switch (action.type) {
  case 'INCREMENT':
    return state + 1
  case 'DECREMENT':
    return state - 1
  default:
    return state;
  }
}

// 創(chuàng)建 Redux store 來存放應(yīng)用的狀態(tài)
// API 是 { subscribe, dispatch, getState }
const store = createStore(counter);

// 可以手動(dòng)訂閱更新,也可以事件綁定到視圖層枪汪。
store.subscribe(() =>
  const sotreState = store.getState()
  ......
)

// 改變內(nèi)部 state 惟一方法是 dispatch 一個(gè) action涌穆。
store.dispatch({ type: 'INCREMENT' })
store.dispatch({ type: 'DECREMENT' })
  1. redux 三大原則:

    • 單一數(shù)據(jù)源:整個(gè)應(yīng)用的 state 存放在唯一的一個(gè) store 中怔昨。store.getState()

    • state 是只讀的,唯一改變 state 的方法就是觸發(fā) action宿稀,action 是一個(gè)用于描述已發(fā)生事件的普通對(duì)象趁舀。

    store.dispatch({
      type: 'COMPLETE_TODO',
      index: 1
    })
    
    • 使用純函數(shù)來執(zhí)行修改(reducer:接收先前的 state 和 action,并返回新的 state)
    function visibilityFilter(state = 'SHOW_ALL', action) {
      switch (action.type) {
        case 'SET_VISIBILITY_FILTER':
          return action.filter
        default:
          return state
      }
    }
    
    function todos(state = [], action) {
      switch (action.type) {
        case 'ADD_TODO':
          return [
            ...state,
            {
              text: action.text,
              completed: false
            }
          ]
        case 'COMPLETE_TODO':
          return state.map((todo, index) => {
            if (index === action.index) {
              return Object.assign({}, todo, {
            completed: true
              })
            }
            return todo
          })
        default:
          return state
      }
    }
    
    import { combineReducers, createStore } from 'redux'
    const reducer = combineReducers({ visibilityFilter, todos })
    const store = createStore(reducer)
    

React-Redux

npm install react-redux --save

React-ReduxRedux 的官方 React 綁定庫祝沸。它能夠使你的 React 組件從 Redux store 中讀取數(shù)據(jù)矮烹,并且向 store 分發(fā) actions 以更新數(shù)據(jù)

  1. React-Redux 將所有組件分成兩大類:UI 組件和容器組件。UI 組件負(fù)責(zé) UI 的呈現(xiàn)罩锐,容器組件負(fù)責(zé)管理數(shù)據(jù)和邏輯奉狈。

    • UI 組件:只負(fù)責(zé) UI 的呈現(xiàn),不帶有任何業(yè)務(wù)邏輯涩惑;沒有狀態(tài)(即不使用 this.state 這個(gè)變量)仁期;所有數(shù)據(jù)都由參數(shù) this.props 提供;不使用任何 ReduxAPI
    • 容器組件:負(fù)責(zé)管理數(shù)據(jù)和業(yè)務(wù)邏輯竭恬,不負(fù)責(zé) UI 的呈現(xiàn)跛蛋;帶有內(nèi)部狀態(tài);使用 ReduxAPI痊硕。
  2. React-Redux 規(guī)定赊级,所有的 UI 組件都由用戶提供,容器組件則是由 React-Redux 自動(dòng)生成岔绸。也就是說理逊,用戶負(fù)責(zé)視覺層,狀態(tài)管理則是全部交給它盒揉。

  3. connect()

import { connect } from 'react-redux'
const VisibleTodoList = connect(mapStateToProps, mapDispatchToProps)(TodoList)

上面 VisibleTodoList 便是 UI 組件 TodoList 通過 connect 方法自動(dòng)生成的容器組件晋被。

connect 方法接受兩個(gè)參數(shù):mapStateToPropsmapDispatchToProps。它們定義了 UI 組件的業(yè)務(wù)邏輯预烙。前者負(fù)責(zé)輸入邏輯墨微,即將 state 映射到 UI 組件的參數(shù) props,后者負(fù)責(zé)輸出邏輯扁掸,即將用戶對(duì) UI 組件的操作映射成 Action

  1. mapStateToProps()
const mapStateToProps = (state) => {
  return {
    todos: getVisibleTodos(state.todos, state.visibilityFilter)
  }
}

mapStateToProps 是一個(gè)函數(shù)最域,它接受 state 作為參數(shù)谴分,返回一個(gè)對(duì)象。這個(gè)對(duì)象有一個(gè) todos 屬性镀脂,代表 UI 組件的同名參數(shù)牺蹄,后面的 getVisibleTodos 也是一個(gè)函數(shù),可以從 state 算出 todos 的值薄翅。
mapStateToProps 建立一個(gè)從(外部的)state 對(duì)象到(UI 組件的)props 對(duì)象的映射關(guān)系沙兰。執(zhí)行后應(yīng)該返回一個(gè)對(duì)象氓奈,里面的每一個(gè)鍵值對(duì)就是一個(gè)映射。

  1. mapDispatchToProps()
    mapDispatchToProps 用來建立 UI 組件的參數(shù)到 store.dispatch 方法的映射鼎天。它定義了哪些用戶的操作應(yīng)該當(dāng)作 Action舀奶,傳給 Store。它可以是一個(gè)函數(shù)斋射,也可以是一個(gè)對(duì)象育勺。
  • 是函數(shù)則會(huì)得到 dispatchownProps(容器組件的 props 對(duì)象)兩個(gè)參數(shù)。
const mapDispatchToProps = (dispatch, ownProps) => {
  return {
    onClick: () => {
      dispatch({
        type: 'SET_VISIBILITY_FILTER',
        filter: ownProps.filter,
      })
    }
  }
}
  • 是一個(gè)對(duì)象罗岖,它的每個(gè)鍵名也是對(duì)應(yīng) UI 組件的同名參數(shù)涧至,鍵值應(yīng)該是一個(gè)函數(shù),會(huì)被當(dāng)作 Action creator 桑包,返回的 Action 會(huì)由 Redux 自動(dòng)發(fā)出南蓬。
const mapDispatchToProps = {
  onClick: (filter) => {
    type: 'SET_VISIBILITY_FILTER',
    filter: filter
  };
}
  1. <Provider> 組件
    connect 方法生成容器組件以后,需要讓容器組件拿到 state 對(duì)象哑了,才能生成 UI 組件的參數(shù)蓖康。
    React-Redux 提供 Provider 組件,使整個(gè) app 訪問到 Redux store 中的數(shù)據(jù) 即state垒手。
// src/index.tsx
import React from 'react'
import ReactDOM from 'react-dom'
import reportWebVitals from './reportWebVitals'
import { Provider } from 'react-redux'
import store from './redux/store'
import App from './App'

ReactDOM.render(
    <React.StrictMode>
        <Provider store={store}>
            <App />
        </Provider>
    </React.StrictMode>,
    document.getElementById('root')
)

reportWebVitals()
  1. 實(shí)戰(zhàn):國(guó)際化
    npm install redux react-redux react-i18next i18next --save
  • redux 封裝在類組件中使用
// src/index.tsx
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'
import reportWebVitals from './reportWebVitals'
import { Provider } from 'react-redux'
import store from './redux/store'

ReactDOM.render(
    <React.StrictMode>
        <Provider store={store}>
            <App />
        </Provider>
    </React.StrictMode>,
    document.getElementById('root')
)
reportWebVitals();
// src/App.tsx
import React from 'react'
import { BrowserRouter, Route, Switch } from 'react-router-dom'
import styles from './App.module.css'
import { HomePage, LoginPage, DetailPage } from './pages'
import './i18n/configs'

function App() {
    return (
        <div className={styles.app}>
            <BrowserRouter>
                <Switch>
                    <Route exact path="/" component={HomePage} />
                    <Route path="/login" component={LoginPage} />
                    <Route path="/detail/:id" component={DetailPage} />
                </Switch>
            </BrowserRouter>
        </div>
    )
}

export default App
// src/pages/home/Home.tsx
import React from 'react'
import styles from './Home.module.css'
import { Header, Footer } from '../../components'
import { RouteComponentProps, withRouter } from 'react-router-dom'
import { withTranslation, WithTranslation } from 'react-i18next'

class HomePageComponent extends React.Component<RouteComponentProps & WithTranslation> {
    render() {
        const { t } = this.props

        return (
            <>
                <Header />
                <div>{t('home_page.content')}</div>
                <Footer />
            </>
        )
    }
}

export const HomePage = withTranslation()(withRouter(HomePageComponent))
// src/i18n/configs.ts
import i18n from 'i18next'
import { initReactI18next } from 'react-i18next'

import translation_en from './en.json' // 英文配置
import translation_zh from './zh.json' // 中文配置

const resources = {
    en: { translation: translation_en },
    zh: { translation: translation_zh },
}

i18n
    .use(initReactI18next)
    .init({
        resources,
        lng: 'zh',
        interpolation: { escapeValue: false },
    })

export default i18n
// src/components/header/Header.class.tsx
import React from 'react'
import { GlobalOutlined } from '@ant-design/icons'
import { Layout, Typography, Dropdown, Menu, Button, Input } from 'antd'
import styles from './Header.module.css'
import { RouteComponentProps, withRouter } from 'react-router-dom'
import { RootState } from '../../redux/store'
import { withTranslation, WithTranslation } from 'react-i18next'

import { addLanguageActionCreator, changeLanguageActionCreator } from '../../redux/language/languageActions'
import { connect } from 'react-redux'
import { Dispatch } from 'redux'

const mapStateToProps = (state: RootState) => {
    return {
        language: state.language,
        languageList: state.languageList,
    }
}

const mapDispatchToProps = (dispatch: Dispatch) => {
    return {
        changeLanguage: (code: 'zh' | 'en') => dispatch(changeLanguageActionCreator(code)),
        addLanguage: (name: string, code: string) => dispatch(addLanguageActionCreator(name, code))
    }
}

type PropsType = RouteComponentProps // react-router 路由 props 類型
    & WithTranslation // i18n props 類型
    & ReturnType<typeof mapStateToProps> // redux store 映射類型
    & ReturnType<typeof mapDispatchToProps> // redux dispatch 映射類型

class HeaderComponent extends React.Component<PropsType> {

    toggleLanguage = (event) => {
        this.props.changeLanguage(event.key)
    }

    addLanguage = () => {
        this.props.addLanguage('新語言', 'new_lang')
    }

    render() {
        const { history, t } = this.props

        return (
            <div className={styles['app-header']}>
                <div className={styles['top-header']}>
                    <div className={styles.inner}>
                        <Typography.Text>{t('header.slogan')}</Typography.Text>
                        <Dropdown.Button
                            style={{ marginLeft: 15 }}
                            overlay={
                                <Menu>
                                    {
                                        this.props.languageList.map(item => {
                                            return <Menu.Item key={item.code} onClick={this.toggleLanguage}>{item.name}</Menu.Item>
                                        })
                                    }
                                    <Menu.Item onClick={this.addLanguage}>{t('header.add_new_language')}</Menu.Item>
                                </Menu>
                            }
                            icon={<GlobalOutlined />}
                        >
                            { this.props.language === 'en' ? 'English' : '中文' }
                        </Dropdown.Button>

                        <Button.Group className={styles['button-group']}>
                            <Button onClick={() => history.push('/register')}>{t('header.register')}</Button>
                            <Button onClick={() => history.push('/login')}>{t('header.signin')}</Button>
                        </Button.Group>
                    </div>
                </div>
            </div>
        )
    }
}

export const Header = connect(mapStateToProps, mapDispatchToProps)(withTranslation()(withRouter(HeaderComponent)))
// src/redux/store.ts
import { createStore } from 'redux'
import { languageReducer } from './language/languageReducer'

const store = createStore(languageReducer)

export type RootState = ReturnType<typeof store.getState>

export default store
// src/redux/language/languageActions.ts
export const CHANGE_LANGUAGE = 'changeLanguage'

export const ADD_LANGUAGE = 'addLanguage'

interface changeLanguageAction {
    type: typeof CHANGE_LANGUAGE,
    payload: 'zh' | 'en',
}

interface addLanguageAction {
    type: typeof ADD_LANGUAGE,
    payload: { name: string, code: string},
}

export type LanguageActionTypes = changeLanguageAction | addLanguageAction

export const changeLanguageActionCreator = (languageCode: 'zh' | 'en'): changeLanguageAction => {
    return {
        type: CHANGE_LANGUAGE,
        payload: languageCode,
    }
}

export const addLanguageActionCreator = (name: string, code: string): addLanguageAction => {
    return {
        type: ADD_LANGUAGE,
        payload: { name, code },
    }
}
// src/redux/language/languageReducer.ts
import i18n from 'i18next'
import { ADD_LANGUAGE, CHANGE_LANGUAGE, LanguageActionTypes } from './languageActions'

export interface LanguageState {
    language: 'en' | 'zh'
    languageList: { name: string, code: string }[]
}

const defaultState: LanguageState = {
    language: 'zh',
    languageList: [
        { name: 'English', code: 'en' },
        { name: '中文', code: 'zh' },
    ],
}

export const languageReducer = (state = defaultState, action: LanguageActionTypes): LanguageState => {
    const { type, payload } = action

    switch (type) {
        case CHANGE_LANGUAGE:
            i18n.changeLanguage(payload as string)
            return { ...state, language: payload as 'en' | 'zh' }
        case ADD_LANGUAGE:
            return { ...state, languageList: [ ...state.languageList, payload as { name: string, code: string } ]}
        default:
            return state
    }
}
  • redux 封裝在函數(shù)式組件中使用
// src/redux/hooks.ts
import { useSelector, TypedUseSelectorHook } from 'react-redux'
import { RootState } from './store'

export const useReduxSelector: TypedUseSelectorHook<RootState> = useSelector
// src/components/header/Header.tsx
import React from 'react'
import { GlobalOutlined } from '@ant-design/icons'
import { Layout, Typography, Dropdown, Menu, Button, Input } from 'antd'
import styles from './Header.module.css'
import { useHistory } from 'react-router-dom'
import { useTranslation } from 'react-i18next'

import { addLanguageActionCreator, changeLanguageActionCreator } from '../../redux/language/languageActions'
import { useDispatch } from 'react-redux'
import { useReduxSelector } from '../../redux/hooks'

export const Header: React.FC = () => {
    const history = useHistory()

    const language = useReduxSelector(state => state.language)

    const languageList = useReduxSelector(state => state.languageList)

    const { t } = useTranslation()

    const dispatch = useDispatch()

    const toggleLanguage = (event) => {
        dispatch(changeLanguageActionCreator(event.key))
    }

    const addLanguage = () => {
        dispatch(addLanguageActionCreator('新語言', 'new_lang'))
    }

    return (
        <div className={styles['app-header']}>
            <div className={styles['top-header']}>
                <div className={styles.inner}>
                    <Typography.Text>{t('header.slogan')}</Typography.Text>
                    <Dropdown.Button
                        style={{ marginLeft: 15 }}
                        overlay={
                            <Menu>
                                {
                                    languageList.map(item => {
                                        return <Menu.Item key={item.code} onClick={toggleLanguage}>{item.name}</Menu.Item>
                                    })
                                }
                                <Menu.Item onClick={addLanguage}>{t('header.add_new_language')}</Menu.Item>
                            </Menu>
                        }
                        icon={<GlobalOutlined />}
                    >
                        { language === 'en' ? 'English' : '中文' }
                    </Dropdown.Button>

                    <Button.Group className={styles['button-group']}>
                        <Button onClick={() => history.push('/register')}>{t('header.register')}</Button>
                        <Button onClick={() => history.push('/login')}>{t('header.signin')}</Button>
                    </Button.Group>
                </div>
            </div>
        </div>
    )
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蒜焊,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子科贬,更是在濱河造成了極大的恐慌泳梆,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,607評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件榜掌,死亡現(xiàn)場(chǎng)離奇詭異优妙,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)憎账,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門套硼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人胞皱,你說我怎么就攤上這事邪意。” “怎么了反砌?”我有些...
    開封第一講書人閱讀 164,960評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵雾鬼,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我宴树,道長(zhǎng)策菜,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,750評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮又憨,結(jié)果婚禮上翠霍,老公的妹妹穿的比我還像新娘。我一直安慰自己蠢莺,他們只是感情好寒匙,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著浪秘,像睡著了一般蒋情。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上耸携,一...
    開封第一講書人閱讀 51,604評(píng)論 1 305
  • 那天棵癣,我揣著相機(jī)與錄音,去河邊找鬼夺衍。 笑死狈谊,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的沟沙。 我是一名探鬼主播河劝,決...
    沈念sama閱讀 40,347評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼矛紫!你這毒婦竟也來了赎瞎?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,253評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤颊咬,失蹤者是張志新(化名)和其女友劉穎务甥,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體喳篇,經(jīng)...
    沈念sama閱讀 45,702評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡敞临,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了麸澜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挺尿。...
    茶點(diǎn)故事閱讀 40,015評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖炊邦,靈堂內(nèi)的尸體忽然破棺而出编矾,到底是詐尸還是另有隱情哥蔚,我是刑警寧澤,帶...
    沈念sama閱讀 35,734評(píng)論 5 346
  • 正文 年R本政府宣布温算,位于F島的核電站嗡综,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏悴势。R本人自食惡果不足惜蹭劈,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評(píng)論 3 330
  • 文/蒙蒙 一酿傍、第九天 我趴在偏房一處隱蔽的房頂上張望炉媒。 院中可真熱鬧踪区,春花似錦、人聲如沸吊骤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽白粉。三九已至传泊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鸭巴,已是汗流浹背眷细。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鹃祖,地道東北人溪椎。 一個(gè)月前我還...
    沈念sama閱讀 48,216評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像恬口,于是被迫代替她去往敵國(guó)和親校读。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容