React我的入门总结《四》

简介

这一次总结主要是针对 React 的亮点 Reduxhtml

在什么样的状况下适合使用 React-reduxredux

  1. 某个组件的状态,须要共享。
  2. 某个状态须要在任何地方均可以拿到。
  3. 一个组件须要改变全局状态。
  4. 一个组件须要改变另外一个组件的状态。

Redux

Redux 是一种架构模式,有三大特性 StoreActionsReducersStore 是保存数据的地方,整个应用只能有一个。Actionsview 发出的通知,监听 state 的变化。 Reducers 是一个纯函数,返回新的 state数组

  • 优雅地修改共享状态。bash

    const appState = {
        title: {
            text: '这里是标题',
            color: 'red',
        },
        content: {
            text: '内容内容内容',
            color: 'blue'
        }
    }
    
    // 渲染函数
    function renderApp(appState) {
        renderTitle(appState.title)
        renderContent(appState.content)
    }
    
    // 渲染标题
    function renderTitle(title) {
        const titleDOM = document.getElementById('title')
        titleDOM.innerHTML = title.text
        titleDOM.style.color = title.color
    }
    
    // 渲染内容
    function renderContent(content) {
        const contentDOM = document.getElementById('content')
        contentDOM.innerHTML = content.text
        contentDOM.style.color = content.color
    }
    
    renderApp(appState)    
    复制代码

    上面的 appState 是一个共享状态,随时会被修改,这是不可预料的,有时候一些模块须要这个共享状态而且还须要修改它,这时能够声明一个函数来监听哪一处修改数据。架构

    // 监听应用状态修改
    function dispatch(action) {
        switch (action.type) {
            case 'UPDATE_TITLE_TEXT':
                appState.title.text = action.text
                break
            case 'UPDATE_TITLE_COLOR':
                appState.title.color = action.color
                break
            default:
                break
        }
    }
    
    dispatch({ type: 'UPDATE_TITLE_TEXT', text: '我已经修改了标题和颜色' }) // 修改标题文本
    dispatch({ type: 'UPDATE_TITLE_COLOR', color: 'green' }) // 修改标题颜色
    renderApp(appState) // 渲染
    复制代码

    这样就不怕 renderApp() 执行以前的其余函数会对它们作什么修改,若是某个函数修改了 title.text 可是我并不想要它这么干,我须要 debug 出来是哪一个函数修改了,我只须要在 dispatchswitch 的第一个 case 内部打个断点就能够调试出来了。app

  • 抽离Store。函数

    接下来会把 appStatedispatch 抽出来放在一个地方,把这个地方叫做 store ,而且构建一个函数叫 createStore 用来专门生产这种 statedispatch 的集合。post

    <!-- 接受一个 应用状态 和 描述应用程序状态函数( 至关上面的dispatch ) -->
    function createStore (state, stateChanger) {
        <!-- 直接返回 state -->
        const getState = () => state
        
        <!-- 接受一个 state 还有一个 根据 action 来修改 state 的函数( 至关上面的dispatch ) -->
        const dispatch = (action) => stateChanger(state, action)
        
        <!-- 返回 state 和 dispatch -->
        return { getState, dispatch }
    }
    复制代码

    以后咱们来使用它:性能

    <!-- 监听应用状态修改 (至关于上面的 dispatch 函数) -->
    <!-- 接受 应用状态 和 修改应用状态的信息 -->
    function stateChanger(state, action) {
        switch (action.type) {
            case 'UPDATE_TITLE_TEXT':
                state.title.text = action.text
                break
            case 'UPDATE_TITLE_COLOR':
                state.title.color = action.color
                break
            default:
                break
        }
    }
    
    // store
    function createStore(state, stateChanger) {
       const getState = () => state;
       const dispatch = (action) => stateChanger(state, action);
       return {getState, dispatch}        
    }
    
    <!-- 传入 应用状态 和 修改应用状态的函数 -->
    const store = createStore(appState, stateChanger);
    
    renderApp(store.getState()) // 第一次渲染
    store.dispatch({type: 'UPDATE_TITLE_TEXT', text: '老子修改了标题'}); // 发送 action
    store.dispatch({type: 'UPDATE_TITLE_COLOR', color: 'green'}); // 发送 action
    renderApp(store.getState()) // 从新渲染
    复制代码
    • 监听数据变化

    上面每次经过 dispatch 修改数据时候,若是不手动调用 renderApp 页面是不会发生变化的,咱们能够利用观察者模式,往 dispatch 加入 renderApp 就好了。ui

    function createStore(state, stateChanger) {
        <!-- 声明空数组 -->
        const listeners = []; 
        <!-- 每次调用接受渲染函数做为参数而且添加到空数组 -->
        const subscribe = (listener) => listeners.push(listener);
        const getState = () => state;
        const dispatch = (action) => {
            stateChanger(state, action);
            <!-- 把全部渲染函数遍历而且调用,这样其余的渲染也会从新渲染 -->
            listeners.forEach((listener) => listener());
        }
        return {getState, dispatch, subscribe}        
    }
    
    const store = createStore(appState, stateChanger)
    <!-- 下面能够传递多个渲染函数,而后调用 dispatch 所有会从新渲染 -->
    store.subscribe(() => renderApp(store.getState()))
    store.subscribe(() => renderApp2(store.getState()))
    store.subscribe(() => renderApp3(store.getState()))
    复制代码

    上面 subscribe 是订阅者,这样的订阅模式,不管你利用 dispatch进行修改数据,他都会从新渲染,还有一个好处就是拿同一块数据来渲染别的页面时, dispatch 致使的变化也会让每一个页面都从新渲染。

    总结来讲就是构建一个 createStore ,它能够产生一种咱们新定义的数据类型 store,经过 store.getState 咱们获取共享状态,并且咱们约定只能经过 store.dispatch 修改共享状态。store 也容许咱们经过 store.subscribe 监听数据数据状态被修改了,而且进行后续的例如从新渲染页面的操做。

  • 共享结构的对象提升性能。

    // 渲染函数
    function renderApp(appState) {
        console.log('渲染所有')
        renderTitle(appState.title)
        renderContent(appState.content)
    }
    
    // 渲染标题
    function renderTitle(title) {
        console.log('渲染标题')
        const titleDOM = document.getElementById('title')
        titleDOM.innerHTML = title.text
        titleDOM.style.color = title.color
    }
    
    // 渲染内容
    function renderContent(content) {
        console.log('渲染内容')
        const contentDOM = document.getElementById('content')
        contentDOM.innerHTML = content.text
        contentDOM.style.color = content.color
    }
    
    renderApp(store.getState()) // 第一次渲染
    store.dispatch({type: 'UPDATE_TITLE_TEXT', text: '老子修改了标题'});
    store.dispatch({type: 'UPDATE_TITLE_COLOR', color: 'green'});
    复制代码

    在上面调用时会从新渲染三次,这会有一个很严重的性能问题,我并无修改 content 的东西,也会从新渲染 content

    这里提出的解决方案是,在每一个渲染函数执行渲染操做以前先作个判断,判断传入的新数据和旧的数据是否是相同,相同的话就不渲染了。

    // 渲染函数
    function renderApp(newAppState, oldAppState = {}) {
        if (newAppState === oldAppState) return // 数据没有变化就不渲染了
        console.log('渲染所有')
        renderTitle(newAppState.title, oldAppState.title)
        renderContent(newAppState.content, oldAppState.content)
    }
    
    // 渲染标题
    function renderTitle(newTitle, oldTitle = {}) {
        if (newTitle === oldTitle) return // 数据没有变化就不渲染了
        console.log('渲染标题')
        const titleDOM = document.getElementById('title')
        titleDOM.innerHTML = newTitle.text
        titleDOM.style.color = newTitle.color
    }
    
    // 渲染内容
    function renderContent(newContent, oldContent = {}) {
        if (newContent === oldContent) return // 数据没有变化就不渲染了
        console.log('渲染内容')
        const contentDOM = document.getElementById('content')
        contentDOM.innerHTML = newContent.text
        contentDOM.style.color = newContent.color
    }
    
    
    // 监听应用状态修改
    function stateChanger(state, action) {
        switch (action.type) {
            case 'UPDATE_TITLE_TEXT':
                return {
                    ...state,
                    title: {
                        ...state.title,
                        text: action.text
                    }
                }
            case 'UPDATE_TITLE_COLOR':
                return {
                    ...state,
                    title: {
                        ...state.title,
                        color: action.color
                    }
                }
            default:
                return state
        }
    }
    
    // store
    function createStore(state, stateChanger) {
        const listeners = [];
        const subscribe = (listener) => listeners.push(listener);
        const getState = () => state;
        const dispatch = (action) => {
            state = stateChanger(state, action); // 从新覆盖 state
            listeners.forEach((listener) => listener());
        }
        return { getState, dispatch, subscribe }
    }
    
    const store = createStore(appState, stateChanger);
    let oldState = store.getState(); // 保存旧状态
    store.subscribe(() => {
        const newState = store.getState();
        renderApp(newState, oldState);
        oldState = newState; // 每次更改让旧的状态变成新的状态
    })
    
    renderApp(store.getState()) // 第一次渲染
    store.dispatch({ type: 'UPDATE_TITLE_TEXT', text: '老子修改了标题' });
    store.dispatch({ type: 'UPDATE_TITLE_COLOR', color: 'green' });
    复制代码

    上面那三个是第一次渲染触发的,下面那两次修改从新渲染时并无从新渲染 content

    因为上面须要保存一个旧的状态,而后根据旧的状态和新的状态进行判断,上面的 stateChanger 修改为返回一个新的状态,这个新的状态与旧的状态是相对独立的,还有就是 store 里面的 dispatch ,每次进行修改就用一个 state 接受一个新的状态而且覆盖原来的 state ,最后再将新的状态和旧的状态传达给 renderApp

  • reducer

    reducer 是一个纯函数,它接受两个参数,一个是 state ,一个是 action ,它的做用是计算 state 而且返回一个新的 state ,若是没有传入值或没法识别 action 时,会直接返回 state

    function themeReducer (state, action) {
      if (!state) return {
        themeName: 'Red Theme',
        themeColor: 'red'
      }
      switch (action.type) {
        case 'UPATE_THEME_NAME':
          return { ...state, themeName: action.themeName }
        case 'UPATE_THEME_COLOR':
          return { ...state, themeColor: action.themeColor }
        default:
          return state
      }
    }
    
    function createStore(reducer) {
        let state = null; // 初始化状态
        const listeners = [];
        const subscribe = (listener) => listeners.push(listener);
        const getState = () => state;
        const dispatch = (action) => {
            state = reducer(state, action);
            listeners.forEach((listener) => listener());
        }
        dispatch({}) // 初始化
        return { getState, dispatch, subscribe }
    }
    复制代码

    Store 收到 Action 之后,必须给出一个新的 State ,这样 View 才会发生变化。这种 State 的计算过程就叫作 ReducerReducer 是一个纯函数,它接受 Action 和当前 State 做为参数,返回一个新的 State

总结

redux 的使用有如下步骤:

// 定一个 reducer
function reducer (state, action) {
  /* 初始化 state 和 switch case */
}

// 生成 store
const store = createStore(reducer)

// 监听数据变化从新渲染页面
store.subscribe(() => renderApp(store.getState()))

// 首次渲染页面
renderApp(store.getState()) 

// 后面能够随意 dispatch 了,页面自动更新
store.dispatch(...)
复制代码

上一篇 --- React我的入门总结《三》

下一篇 --- React我的入门总结《五》

相关文章
相关标签/搜索