redux 原理

store 实例持有当前状态

const createStore = (reducer) => {
    let state = {};
    let listeners = []

    getState = () => state;

    dispatch(listener) {
            state = reducer(state, action);
            listeners.forEach(listener => listener())
        },

        subscribe(listener) {

            listeners.push(listener)

            return () => {
                listeners = listeners.filter(item => {
                    item !== listener
                })
            }
        }

    return {
        getState,
        dispatch,
        subscribe
    }
}

if (process.env.NODE_ENV== 'production') {
        const store.dispatch = addLoggingToDispatch(store);
    }

    addLoggingToDispatch(store) => {
        const rawDispatch = store.dispatch;

        if (!console.log(group)) {
            return rawDispatch
        }

        return (action) => {
            console.log('dayin', get.state())

            //调用原始的 dispatch 并记录返回值
            const returnValue = rawDispatch(action);

            console.log('dayin', get.state())
            return returnValue
        }
    }

    addPromiseDispatch(store) => {
        const rawDispatch = store.dispatch;

        return (action) => {
            if (typeof action.then() == 'function') {
                return action.then(rawDispatch)
            }

            return rawDispatch(action)
        }
    }

我们可以将这种包装过程收敛 -- 声明一个数组, 即中间件数组 Redux 核心思想就是将 dispatch 增强改造的函数(中间件)先存起来,然后提供给 Redux.

Redux 负责依次执行. 这样每一个中间件都对 dispatch 一次进行改造, 并将改造后的 dispatch 即 next 向下传递, 即将控制权转移给下一个中间件, 完成进一步的增强

const configureStore = () => (
  const store= createStore(App) ;
  const middlewares = [];
  if (process . env . NODE_ENV !== 'production') {

    middlewares.push(addLoggingToDispatch);

  }
  middlewares.push(addPromiseSupportToDispatch);
  wrapDispatchWithMiddlewares(store , middlewares)
  return store;
}

wrapDispatchWithMiddlewares 接收一个 middlewares 数组 和最纯净的 store.dispatch

const wrapDispatchWithMiddlewares = (store, middlewares) => {
  middlewares
    .slice()
    .reverse()
    .forEach(
      (middleware) => (store.dispatch = middleware(store)(store.dispatch))
    );
};

const promise = (store) => (next) => (action) => {
  return (action) => {
    if (typeof action.then() == "function") {
      return action.then(next);
    }

    return next(action);
  };
};

const logger = (store) => (next) => {
  if (!console.log(group)) {
    return next;
  }
  return (action) => {
    console.log("dayin", get.state());

    //调用原始的 dispatch 并记录返回值
    const returnValue = next(action);

    console.log("dayin", get.state());
    return returnValue;
  };
};
export default function applyMiddleware(...middlewares) {
  return (next) => (reducer, initialState) => {
    var store = next(reducer, initialState);
    var dispatch = store.dispatch;
    var chain = [];

    var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action),
    };

    chain = middlewares.map((middleware) => middleware(middlewareAPI));

    dispatch = compose(...chain, store.dispatch);

    return {
      ...store,
      dispatch,
    };
  };
}

middlewareAPI 是第三方中间件需要使用的参数,即原始的 store.getState 和 dispatch 方法, 这些参数在中间件中是否会全部应用到,自然要看每个中间件的应用场景和需求。

chain 数组中的每一项都是对原始 dispatch 的增强,并进行控制权转移. 所以就有了 dispatch = compose(...chain, store.dispatch)

这里的 dispatch 函数就是增强后的 dispatch. 因此, compose 方法接收了 chain 数组和原始的 store.dispatch 方法.

export default function compose(...funcs) {
  if (funcs.length === 0) {
    return (arg) => arg;
  }

  if (funcs.length === 1) {
    return funcs[0];
  }

  return funcs.reduce(
    (a, b) =>
      (...args) =>
        a(b(...args))
  );
}

写一个中间件的套路

const customMiddleware = (store) => (next) => (action) => {};
store.dispatch({
  type: 'CHANGE_THEME',
  payload: 'light'
})


const CHANGE_THEME = store => next => action => {
  //拦截目标 action

  if(action.type  === 'CHANGE_THEME'){
    if(localStorage.getItem('theme') !=== action.payload){
      localStorage.setItem('theme', action.payload)
    }
  }

  return next(action)
}

// 业务初始化的时候
store.dispatch({
  type: 'CHANGE_THEME',
  payload: localStorage.getItem('theme') || 'dark'
})

可组合的 reducer

我们可以记录每一个 action 和状态,当程序出现问题时, 首先查询究竟是哪一个状态发生错误,然后回溯,看前一个触发错误的 action 是否准确.因为 action 都是 JavaScript 对象,完全可读,所以我们完全有能力识别出 action 的正确性. 在持久性方面 数据采用 JavaScript 象来存储,可以方便地使用 Ison parse 等相关序列 方法,可以在任何需要数据的地方进行 载。同时,对于同构渲染,我们也可以在服务端就进行状态的设定,完成服务端直 。当页 面出现错误时,我们可以用典型的 try ... catch 捕获错误,并记录当前的状态以及 action ,这对于 生成页面错误报告也是非常有意义 对于优化共 ,我们可以设想这样的场景:在页面中, 一个用户可以点击关注按钮去关注另外一个用户。在点击关注之后 ,可以在发送网络请求之前 便对 state 进行最初的更改,同时把相关的 action 记录在一个队列中 ,稍后再发送关注的请求 如果请求最终失败,则返回相应的状态,并提示用户.

const getVisibleTodos = (state , filter) => {
  switch (filter) {
    case  'all' :
    return state ;
    case 'completed' :
    return state.filter => (t => t.completed) ;
  }
}
const undoable = (reducer) => {
  const initialState = {
    past: [],
    present: reducer(undefined, {}),
  };

  return (state = initialState, action) => {
    const { past, present } = state;
    if (action.type === "UNDO") {
      return {
        past: past.slice(0, -1),
        present: past[past.length - 1],
      };
    }
    return {
      past: [...past, present],
      present: reducer(present, action),
    };
  };
};

react-redux

将所有的业务组件嵌套在由 react-redux 提供的 provider 组件当中,并将所生成的 store 设置为 provider 组件的参数, provider 组件便感知到 store.

context 用来使 react 子孙组件可以直接"越级"获取父组件的信息,这样就不需要一层层通过 props 向下传递. 核心目标是使 props.store 放到 context 中; connect 作为一个柯理化的高级函数,可以根据一级参数计算筛选出 store 信息,根据二级参数 component 返回一个高级组件 connectComponent 即可

同构应用

就是用户必须等待 Java Script 脚本加载完成,且真正执行时才会发起数据请求。接下来,等待 数据成功返回后,脚本完成页面内容渲染,用户才可以得到最终页面。这样做直接降低了页面 首屏展现的时间,特别是在移动互联网环境下,对首屏加载性能的影响很大。

不利于 SEO 和存在性能问题 页面的数据内容主要由 JavaScript 脚本动态生成,因此非常不利于搜索引擎获取该页面的信息. 且对首屏加载性能的影响很大

服务端渲染技术会把数据请求过程放在服务端,相对于前后端分离的方式,获取数据更加 提前,页面模板结合数据的渲染处理也在服务端完成。结合 React 技术,基本的组件拼接在服 务端完成 并最终输出相对完整的 HTML 返回给浏览器端。

服务端渲染主要侧重架构层面的实现,而同构更侧重代码复用

所谓同构,就是指前后端共用一套代码或逻辑,而在这套代码或逻辑中,理想的状况是在浏览器端进一步渲染的过程中,判断己有的 DOM 结构和即将渲染出的结构是否相同,若相同,则不重新渲染 DOM 结构,只需要进行事件绑定即可.

同构更像是服务端渲染和浏览器渲染的交集, 它弥补了服务端和浏览器端的差异,从而使得同一套代码或逻辑得以统一运行.

同构得劣势: 增加了服务端得 ttfb 时间,ttfb 时间指得是从浏览器发起最初的网络请求,到从服务器接收到第一个字节得这段时间.它包含了 tcp 连接时间\发送 http 请求得时间和获得相应消息的第一个字节时间.

react 也实现了相应得 api, ReactDomServer 对象可以实现服务端渲染,ReactDOMServer 对象主要提供了 renderToString 和 renderToStaticMarkup()

renderToString 接收一个 react element , 并将此 element 转化为 html 字符串,通过浏览器端返回,因此,在服务端将页面拼接字符串插入 html 文档中并返回给浏览器,完成初步服务端渲染的目的.

renderToStrin 生成的 HTML 字符串的每个 DOM 节点都有一个 data-react-id 属性, 根节点会有一个 data-checksum 属性。

当服务端和浏览器端渲染的组件具有相同的 props 和 dom 结构时,该 react 组件只会渲染一次

React 16 还提供了 renderToNodeStream 法实现服务端渲染。该方法将持续产生字节流, 返回 Readable stream 。最终通过流形式返回的 HTML 字符串,与 renderToString 返回的 HTML 字符串并无差 。

服务端处理 内容时是实时 向浏览器端传输数据 ,而不是一次性处理完成后才开始向浏览器端返回结果的.这样做的好处是可以缩短 ttfb 时间

注意事项:

  • 在服务端并不存在直接组件挂载的浏览器环境,所以 react 组件只有 componentDidMount 之前的生命周期方法有效。因此在 getlnitia!State render 等组件方法 中不能用到浏览器的一些特性,比如访问 localStorage window 等。合理的做法 ,将 依赖浏览器环境的操作放到 componentDidMount 中处理。

  • 在服务端拉取数据后, 在很多场景下浏览器端也需要拉取数据,进行二次渲染.为了实 现更好的代码复用, 种典型的做法是把请求数据的逻辑放到 React 组件的静态方法中。 这样不管是浏览器端还是服务端,在需要获取最新数据时都可以直接访 该方法, 以实 现代码复用

关于数据请求逻辑的问题 因为服务端不存在 JAX 概念,在 Node.js 环境下, 一般 使用 http.request 来完成请求。为了达到代码复用的效果,可以使用 isomorphic tch 包对 请求逻辑的服务端和浏览器端 一致性进行封装。

var onServer = typeof window === ’ undefined ’ ;
if (onServer) {//服务端逻辑) else {//浏览器端逻辑}

在服务端使用 redux 常见的套路和做法

serialize-javascript window.INITIAL_STATE = ${JSON.stringify(store.getState())}

防范:xss serialize-javascript

同构项目实战

这是因为 TTFB 时间是服务器响应首宇节的时间,采用流的渐进式渲染可以 大限度地缩短服务器响应时间,从而使浏览器可以更快地接收到信息

当初始的 HTML 文档被完全加载和解析完成之后, DOMContentLo ade 事件被触发,而无须等待样式表、图片和子框架加载完成: Load 事件用于检测页面是否完全加载完成。具体来说,如果 HTML 文档 中包含脚本,则脚本会阻塞文档的解析,在处理完脚本之后,浏览器再继续解析 HTML 文档。在任何情况下 ,触发 DOMContentLoaded 事件都不需要等待图片等其他资源加载完成。

让 setState 连续更新的方法:

  1. 将一个回调函数传入 setState 方法中, 即著名的函数式用法.

setState((prevState, props) => {
  count: prevState.count + 1;
});
  1. 把 setState 更新之后的逻辑封装到一个函数中,并作为第二个参数传给 setState.这个函数逻辑将会在更新后由 React 代理执行. this.setState(updater, [callback])

  2. 把更新之后的逻辑放在一个合适的生命周期,例如 componentDidMount 或者 componentDidUpdate

react Element 描述了用户在屏幕上看到的事物. 抽象地说, React element 是一个描述了 DOM 节点的对象.babel 会对 jsx -> react.createElement

在数据持久度上,不同状态的数据大体分为三类 快速变更型、中等持续型和长远稳定型数据

快速变更型数据在应用中代表了某些原子级别的信息,其显著特点是变更频率最快 比如一个文本输入框数据值,可能会随着用户输入在短时间内持续发生变化. 这类数据显然更适合 维护在 React 组件之内.

对于中等持续型数据,当用户浏览或使用应用时,这类数据往往会在页面刷新前保持稳定。比如从异步请求接口通过 AJAX 方式得来的数据;或者在个人中心页面,用户编辑信息提交的数据.

长远稳定型数据是指在页面多次刷新或者多次访问期间都保持不变的数据。因为 Redux store 会在每次页面加载后都重新生成,因此这类数据显然应该存储在 Redux 以外的其他地方, 比如服务端数据库或者本地存储中。

Redux store 已经有了 一个现成的方法 reducer ,即 store.replaceReducer 于它,我们可以动态实现传递一个新的 reducer 来取代旧的 reducer 先假设在 上,己经有了初始状态的 reducer 此时如果需要再添加一个 splitReducer 则可 这样做

当任何一个组件使用 setState 方法时, React 都会认为该组件变“脏”了,触发组件本身重新渲染。同时因其始终维护两套虚拟的 DOM ,其中一套是更新后的虚拟的 DOM ;另一套是前一个状态的虚拟的 DOM. 通过对这两套虚拟的 DOM 运用 diff 算法,找到需要变化的最小单元 集,然后把这个最小单元集应用在真实的 DOM 当中.

DOM 点跨层级移动忽略不计。 拥有相同类的两个组件生成相似的树形结构,拥有不同类的两个组件生成不同的树形 结构 React 对组件树进行分层比较,两棵树只会对同一层级的节点进行比较。 ·当对同 一层级的节点进行 较时,对于不 同的组件类型 ,直接将 个组件替换为新类 型组件。

render () {
  return <Myinput o口Change={this props update bind(this)) /> ;
}

或者

render( ) {
  return <Myinput onChange= { () => this . props. update () ) />;
}

使用 bind 方法,每次渲染时都会 建一 新函数,对内存造成不必要的消耗。在完成 this 绑定的情况下,提倡的做法是

onChange() {
  this.props doUpdate()
}
render () {
  return <Myinput onChange={this . onChange)/> ;
}
render () {
return <Subcomponent i terns= {this. props. i terns I I [] } />
}

这样做会在每次渲染且 this.props.items 不存在时都创建 空数 。更好的做法是:

const EMPTY ARRAY= [] ;
render() {
return <Subcomponent tems={th props terns I I EMPTY ARRAY}/>
}

redux 异步中间件原理

异步中间件本质上是增强了 dispatch 函数的功能。它通过中间件机制拦截 action,并在 action 到达 reducer 之前执行一些额外的逻辑(比如异步操作)。然后再决定是否将 action 传递给 reducer。

const middleware = (store) => (next) => (action) => {
  // 在这里处理 action
  // next 是调用下一个中间件或 reducer 的函数
  return next(action);
};
  • store: Redux store 的实例,包含 getState 和 dispatch。

  • next: 一个函数,用于将 action 传递给下一个中间件或最终的 reducer。

  • action: 当前处理的 action。

中间件通过柯里化(currying)依次应用到 dispatch 上,最终形成一个增强版的 dispatch 函数。

redux-thunk 是一个比较常用的中间件,它允许 action 创建函数返回一个函数代替一个 action 对象。这个函数接收 dispatch 和 getState 作为参数,可以在函数体内部执行异步代码。

function thunk({ dispatch, getState }) {
  return (next) => (action) => {
    if (typeof action === "function") {
      // 如果 action 是一个函数,就执行它,并传入 dispatch 和 getState
      return action(dispatch, getState);
    }
    // 否则直接传递给下一个中间件
    return next(action);
  };
}

最后更新于

这有帮助吗?