手把手教你React(三)-数据流

1,514 阅读4分钟

概述

在第一章我们曾经提过,数据流(Data flow)是React的一个很大的特点,在React的思想中页面完全由数据驱动,也就是UI=f(data)。在React中数据包括props和state,其区别在于state主要用来管理组件自身的UI状态,而props主要用于组件之间的通信。同时React的数据流向最重要的特点在于它是单向的,也就是单向数据流。在这样的的数据流转方式下,当用户修改了一个组件的状态时,React会自动的更新其子孙组件的UI。

组件之间的通信

父子组件的通信

父子组件的通信是React最基础的通信,父组件只需将需要传递的内容通过组件属性传递给子组件就行:

class Father extends React.Component {
  render() {
    return (
      <Child {...props} />
    );
  }
}

在React的单向数据流中并不允许子组件直接修改或向父组件传递props,子组件向父组件通信需要通过回调函数的方式实现。

class Father extends React.Component {
handleClick(name) {
  console.log(`click ${name}`);
}  render() {
    return (
      <Child {...props} handleClick={this.handleClick} />
    );
  }
}

class Child extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      name: 'child'
    }
  }
  click() {
    this.props.handleClick && this.props.handleClick(this.state.name);
  }

  render() {
    return (
      <div onClick={this.click}>click</div>
    );
  }
}

兄弟组件通信

兄弟组件在React中同样无法直接通信,但是可以通过其共同的父组件进行中转来进行通信。其方法也很简单,其中一个子组件按照上述回调函数的方式向父组件传递信息,父组件再通过props传递给另一个组件即可。

跨层级组件通信

上述组件通信有个很明显的问题,那便是多层级的组件通信,当然通过父子关系和props一层层的传递当然可行,但这样的做法明显不太合理,需要一层层传递props或一层层的处理回调,这样会使得代码的可读性和可维护性变得很差。同时有的属性在项目的多个组件中都会用到,类似于这种情况,React提供了context来使多个组件可以共享一些属性。

需要注意的是,如果只是单纯的多层传递组件导致的代码的可读性的降低,React官方并不推荐使用context,而是推荐通过传递组件而非传递属性的方式来解决这个问题:

我们需要将头像的属性传递给Avatar组件。

<Page user={user} avatarSize={avatarSize} />
// ... 渲染出 ...
<PageLayout user={user} avatarSize={avatarSize} />
// ... 渲染出 ...
<NavigationBar user={user} avatarSize={avatarSize} />
// ... 渲染出 ...
<Link href={user.permalink}>
  <Avatar user={user} size={avatarSize} />
</Link>

只有Avatar组件会用到这些参数,这样的传递并不合理,React推荐将Avatar组件传递的方式

function Page(props) {
  const user = props.user;
  const content = <Feed user={user} />;
  const topBar = (
    <NavigationBar>
      <Link href={user.permalink}>
        <Avatar user={user} size={props.avatarSize} />
      </Link>
    </NavigationBar>
  );
  return (
    <PageLayout
      topBar={topBar}
      content={content}
    />
  );
}

Context 能让你将这些数据向组件树下所有的组件进行“广播”,所有的组件都能访问到这些数据,也能访问到后续的数据更新。

import {ThemeContext, themes} from './theme-context';
import ThemeTogglerButton from './theme-toggler-button';

class App extends React.Component {
  constructor(props) {
    super(props);

    this.toggleTheme = () => {
      this.setState(state => ({
        theme:
          state.theme === themes.dark
            ? themes.light
            : themes.dark,
      }));
    };

    this.state = {      
      theme: themes.light,
      toggleTheme: this.toggleTheme,
    };  
   }

  render() {
    // 整个 state 都被传递进 provider    
    return (
      <ThemeContext.Provider value={this.state}>        
        <Content />
      </ThemeContext.Provider>
    );
  }
}

function Content() {
  return (
    <div>
      <ThemeTogglerButton />
    </div>
  );
}

ReactDOM.render(<App />, document.root);
import {ThemeContext} from './theme-context';

function ThemeTogglerButton() {
  // Theme Toggler 按钮不仅仅只获取 theme 值,它也从 context 中获取到一个 toggleTheme 函数  
  return (    
    ThemeContext.Consumer>
      {({theme, toggleTheme}) => (
        <button onClick={toggleTheme}
          style={{backgroundColor: theme.background}}>
          Toggle Theme
        </button>
      )}
    </ThemeContext.Consumer>
  );
}

export default ThemeTogglerButton;

export const ThemeContext = React.createContext({
  theme: themes.dark,  
  toggleTheme: () => {},
});

在这个demo中我们通过createContext创建一个context,然后通过context.Provider来共享给所有的子组件,而子组件可以通过context.Consumer来获取到到共享出的属性。需要注意的是,我们这里向子组件中共享了一个函数,子组件可以通过调用该函数来更新上级组件的状态,实现数据的双向通信。

通过发布订阅模式来进行组件通信

在没有任何关系的组件通信中,比如单页应用中的两个路由页面之间的通信,我们可以使用发布订阅模式来进行通信,我们模拟实现一个简单EventEmitter。

class EventEmitter {
  constructor() {
    this.eventMap = {};
  }

  on(name, fn) {
    this.eventMap[name] ? this.eventMap[name].push(fn) : this.eventMap[name] = [fn];
  }

  emit(name, ...args) {
    if(!this.eventMap.hasOwnProperty(name)) return;
    this.eventMap[name].map((fn) => {
      fn.apply(this, args);
    })
  }

  off(name) {
    if(!this.eventMap[name]) {
      return;
    } else {
      delete this.eventMap[name];
    }
  }
}

通过Redux、Mobx等状态管理工具实现组件通信

相信大家在平时的开发过程当中或多或少都接触过Redux活着Mobx等状态管理工具,状态管理工具主要是用于辅助处理复杂大型应用的状态管理,但同时它们结合redux-react等库同样也能实现各个组件的通信,其原理在于redux-react本质上是使用了React中的context API来实现了储存在store中的状态的共享。由于context API我们已在前文中介绍,而Redux和Mobx我们也会在下一章状态管理中详细介绍,所以在这里我们就不再介绍了。

总结

  1. React通过props和state来处理数据流,其中props主要用来处理组件之间的通信,而state主要用来管理组件自身状态。

  2. 组件之间的通信主要分为:

  3. 父子组件通信,主要通过直接传递props和回调函数来实现

  4. 兄弟组件通信通过共同的父组件进行中转

  5. 跨层级组件可以将组件进行传递,或者使用context来处理

  6. 也可以通过发布订阅模式和状态管理库来实现不同组件的通信