React 中的数据流

95 阅读3分钟

1.父子组件传参:props

2.祖先和后代的传参:基于context

React Context 使用:可以通过Provider组件的value来传递数据,也可以通过调用React.createContext()来产生context,然后在Consumer组件获得context中的数据。

  1. 创建一个上下文对象
const ThemeContext = React.createContext();
export default ThemeContext;
  1. 使用上下文对象

(1)在祖先组件中使用 <ThemeContext.Provider>...</ThemeContext.Provider> 将后代元素包裹起来,并传递参数:

// 祖先组件
<ThemeContext.Provider
        value={{
            supNum,
            oppNum,
            change
        }}>
        <div className="vote-box">
            <div className="header">
                <h2 className="title">React是很棒的前端框架</h2>
                <span className="num">{supNum + oppNum}</span>
            </div>
            <VoteMain />
            <VoteFooter />
        </div>
    </ThemeContext.Provider>

(2)在后代元素中使用 <ThemeContext.Consumer>...</ThemeContext.Consumer> 将内部元素包裹起来,获取参数:

const VoteMain = function VoteMain() {
    return <ThemeContext.Consumer>
        {context => {
            let { supNum, oppNum } = context;
            return <div className="main">
                <p>支持人数:{supNum}人</p>
                <p>反对人数:{oppNum}人</p>
            </div>;
        }}
    </ThemeContext.Consumer>;
};

对于函数组件,后代元素也可以使用 useContext 获取祖先组件传递的参数:

const VoteFooter = function VoteFooter() {
    let { change } = useContext(ThemeContext);
    return <div className="footer">
        <Button type="primary" onClick={change.bind(null, 'sup')}>支持</Button>
        <Button type="primary" danger onClick={change.bind(null, 'opp')}>反对</Button>
    </div>;
};

context 的优缺点:

优点:改变了props需要一层层往下传递的问题,能够让数据在组件树中传递。基于树形结构共享数据的方式,在某个节点组件开启提供context后,所有后代节点组件都可以获取到共享的数据。

缺点:

  • (1)context相当于全局变量, 难以追溯数据源
  • (2)耦合度高,即不利于组件复用也不利于测试
  • (3)当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染。Provider 及其内部 consumer 组件都不受制于 React.memoshouldComponentUpdate 函数,因此当 consumer 组件在其祖先组件退出更新的情况下也能更新

作者:CS_Joe
链接:juejin.cn/post/684490…
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

3.不同页面之间传参:EventEmitter

手写一个 EventEmitter

class EventEmitter {
  eventListeners = {};
  // 监听事件
  on(event, cb) {
    if (!this.eventListeners[event]) {
      this.eventListeners[event] = [];
    }
    this.eventListeners[event].push(cb);
  }
  // 触发事件
  once(event, cb) {
    const onceCb = (...args) => {
      cb(...args);
      this.remove(event, onceCb);
    };
    this.on(event, onceCb);
  }
  emit(event, ...args) {
    const cbs = this.eventListeners[event];
    if (Array.isArray(cbs) && cbs.length) {
      cbs.forEach((cb) => {
        cb(...args);
      });
    }
  }

  remove(event, cb = null) {
    let listeners = this.eventListeners[event];
    if (!listeners) return;
    if (cb) {
      listeners = listeners.filter((listener) => listener === cb);
      this.eventListeners[event] = listeners;
    }
    delete this.eventListeners[event];
  }
}

4.Redux / mobx

Redux 原理:

  • reducer 修改状态
  • subscribe 订阅更新组件的事件
  • dispatch 里调用reducer修改状态,同时触发所有subscribe订阅的事件,更新组件
interface ActionType {
  type: any;
  [k: string]: any;
}
export const createStore = (
  reducer: (state: any, action: ActionType) => object
) => {
  let state: object = reducer(undefined, { type: undefined });
  const listeners: Array<() => void> = [];
  const getState = () => {
    return state;
  };
  const subscribe = (fn: () => void) => {
    if (!listeners.includes(fn)) {
      listeners.push(fn);
    }
    // 返回 unsubscribe 函数,移除这次 push 的 fn
    return () => {
      const index = listeners.indexOf(fn);
      listeners.splice(index, 1);
    };
  };
  const dispatch = (action: ActionType) => {
    listeners.forEach((fn) => fn());
    state = reducer(state, action);
    return action;
  };
  return {
    getState,
    subscribe,
    dispatch,
  };
};

mobx 设计思想:

利用 defineProperty 做数据劫持

Redux 和 mobx 区别:

(1)store 的区别:redux将所有共享的应用数据放在一个大的store中,而mobx是分模块建立store。

(2)设计方式不同:redux 基于发布订阅,而 mobx 基于Object.defineProperty 做数据劫持。所以mobx 中的action 可以直接修改state,而 redux 的 reducer 是 copy 一份 state,然后返回新的 state。

(3)编程思维方式不同:

  • redux 更多的是基于函数式编程思想:reducer是个纯函数不直接改变状态、中间件的串行调用;
  • mobx 更多基于面向对象和响应式编程思想。