【高级React技术】React对Context和API的高级使用以及使用程序管理焦点

74 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第9天,点击查看活动详情

Context

在一个典型的React应用程序中,数据通过props属性从上到下(通过父级和子级)传输,但这种方法对于某些类型的属性(如区域偏好、UI主题)非常麻烦,这是应用程序中许多组件所需的。上下文提供了一种在组件之间共享这些值的方法,而无需通过组件树显式地逐层传递props。

class App extends React.Component {
  render() {
    return <Toolbar theme="dark" />;
  }
}

function Toolbar(props) {
  return (
    <div>
      <ThemedButton theme={props.theme} />
    </div>
  );
}

class ThemedButton extends React.Component {
  render() {
    return <Button theme={this.props.theme} />;
  }
}

Toolbar 组件接受一个额外的“theme”属性,然后传递给 ThemedButton 组件。 如果应用中每一个单独的按钮都需要知道 theme 的值,这会是件很麻烦的事, 因为必须将这个值层层传递所有组件。 上下文旨在共享组件树的“全局”数据,例如当前已验证的用户、主题或首选语言。例如,在以下代码中,我们通过“主题”属性手动调整按钮组件的样式:

<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组件真正需要用户和avatarSize,那么层层传递这两个道具是非常多余的。一旦《阿凡达》组件需要更多顶级组件的道具,你就必须在中间一个一个地添加它们,这将变得非常麻烦。

API

const MyContext = React.createContext(defaultValue);

创建Context对象。当React呈现订阅Context对象的组件时,该组件将从组件树中最接近其自身的匹配提供程序读取当前上下文值。

<MyContext.Provider value={/* 某个值 */}>

只有当组件所在的树中的提供程序不匹配时,defaultValue参数才会生效。这有助于测试组件,而无需使用Provider包装它们。注意:当未定义的值传递给提供程序时,使用者组件的defaultValue将不会生效。 提供程序接收值属性并将其传递给消费组件。提供者可以对应于多个消费组件。也可以嵌套多个提供程序,内层的数据将覆盖外层的数据。

class MyClass extends React.Component {
  componentDidMount() {
    let value = this.context;
  }
  componentDidUpdate() {
    let value = this.context;
  }
  componentWillUnmount() {
    let value = this.context;
  }
  render() {
    let value = this.context;
  }
}
MyClass.contextType = MyContext;

当提供程序的值更改时,将重新呈现其所有内部消耗组件。提供程序及其内部使用者组件不受shouldComponentUpdate函数的约束,因此使用者组件也可以在其祖先组件退出更新时进行更新。

<MyContext.Consumer>
  {value => /* */}
</MyContext.Consumer>

基于 context 值进行渲染

const MyContext = React.createContext(/* some value */);
MyContext.displayName = 'MyDisplayName';

<MyContext.Provider> // "
<MyContext.Consumer> // 

MyDisplayName.Provider" 在 DevTools 中"MyDisplayName.Consumer" 在 DevTools 中

export const themes = {
  light: {
    foreground: '#000000',
    background: '#eeeeee',
  },
  dark: {
    foreground: '#ffffff',
    background: '#222222',
  },
};

export const ThemeContext = React.createContext(
  themes.dark // 默认值
);

有必要从组件树中深度嵌套的组件更新上下文。在此场景中,您可以通过上下文传递函数,使使用者组件更新上下文。 为了确保上下文的快速重新呈现,React需要将每个使用者组件的上下文设置为组件树中的单独节点。

class App extends React.Component {
  render() {
    const {signedInUser, theme} = this.props;

    // 提供初始 context 值的 App 组件
    return (
      <ThemeContext.Provider value={theme}>
        <UserContext.Provider value={signedInUser}>
          <Layout />
        </UserContext.Provider>
      </ThemeContext.Provider>
    );
  }
}

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

因为上下文使用引用标识来决定何时渲染,所以可能存在一些陷阱。当提供者的父组件重新呈现时,可能会在使用者组件中触发意外的呈现。例如,当提供程序每次重新呈现时,以下代码将重新呈现以下所有使用者组件,因为value属性始终分配给新对象:

class App extends React.Component {
  render() {
    return (
      <MyContext.Provider value={{something: 'something'}}>
        <Toolbar />
      </MyContext.Provider>
    );
  }
}

使用程序管理焦点

我们的React应用程序将在运行时不断更改HTML DOM,有时这会导致键盘焦点丢失或意外元素的设置。要解决这个问题,我们需要以编程方式将键盘聚焦在正确的方向。例如,当弹出窗口关闭时,将键盘焦点重置为弹出窗口的打开按钮。

class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    // 创造一个 textInput DOM 元素的 ref
    this.textInput = React.createRef();
  }
  render() {
  /

    return (
      <input
        type="text"
        ref={this.textInput}
      />
    );
  }
}

使用 ref 回调函数以在实例的一个变量中存储文本输入 DOM 元素

function CustomTextInput(props) {
  return (
    <div>
      <input ref={props.inputRef} />
    </div>
  );
}

class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.inputElement = React.createRef();
  }
  render() {
    return (
      <CustomTextInput inputRef={this.inputElement} />
    );
  }
}

React aria modal提供了焦点管理的一个很好的例子。这是一个罕见的完全可访问的模式窗口示例。它不仅将初始焦点设置在取消按钮上(以防止键盘用户意外激活成功操作),并固定窗口中的键盘焦点,还将键盘焦点重置为窗口关闭时打开窗口的元素。 在这里插入图片描述