React 官方文档学习

137 阅读6分钟

安装

npx create-react-app my-app
cd my-app
npm start

核心概念

Hello world

import React from "react";
import ReactDOM from "react-dom";

ReactDOM.render(
  <h1>Hello, world!</h1>,
  document.getElementById('root')
);

JSX简介

  • 可以内嵌参数

    
    const name = "Josh Perez";
    const element = <h1>Hello, {name}</h1>;
    
    function formatName(user) {
      return user.firstName + ' ' + user.lastName;
    }
    const element = <h1>Hello, {formatName(user)}!</h1>;
    
    
  • JSX表达式

    
    function getGreeting(user) {
        if (user) {
       		 return <h1>Hello, {formatName(user)}!</h1>;
        }
        return <h1>Hello, Stranger.</h1>;
    }
    
    
  • JSX特定属性

    
    const element = <img src={user.avatarUrl}></img>;
    
  • JSX 指定子元素

    
    const element = (
      <div>
        <h1>Hello!</h1>
        <h2>Good to see you here.</h2>
      </div>
    );
    

元素渲染

ReactDOM.render()

const element = <h1>Hello, world</h1>;
ReactDOM.render(element, document.getElementById('root'));

组件&Props

  • 当 React 元素为用户自定义组件时,它会将 JSX 所接收的属性(attributes)以及子组件(children)转换为单个对象传递给组件,这个对象被称之为 “props”。
  • 所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。

注意: 组件名称必须以大写字母开头。

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

//使用组件
const element = <Welcome name="Sara" />;
ReactDOM.render(
  element,
  document.getElementById('root')
);

State&生命周期

  • state使用不用直接修改使用setState();

    • this.state.comment = 'Hello';
    • this.setState({comment: 'Hello'});
  • state的更新可能是异步的

    // Correct
    this.setState(function(state, props) {
        return {
        	counter: state.counter + props.increment
        };
    });
    
    
  • 挂载

    componentDidMount() {}
    
  • 卸载

    componentWillUnmount() {}
    
  • 计时器


class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  tick() {
    this.setState({
      date: new Date()
    });
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

事件处理

  • React 事件的命名采用小驼峰式(camelCase),而不是纯小写。
  • 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。
  • 向事件传递参数
    <button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
    <button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
    
  • this指向绑定
    constructor(props) {
        super(props);
        // 为了在回调中使用 `this`,这个绑定是必不可少的
        this.deleteRow = this.deleteRow.bind(this);
    }
      
    <button onClick={() => this.deleteRow()}>Delete Row</button>
    <button onClick={this.deleteRow}>Delete Row</button>
    <button onClick={this.deleteRow.bind(this)}>Delete Row</button>
    
  • 代码示例
    class Toggle extends React.Component {
      constructor(props) {
        super(props);
        this.state = {isToggleOn: true};
    
        // 为了在回调中使用 `this`,这个绑定是必不可少的
        this.handleClick = this.handleClick.bind(this);
      }
    
      handleClick() {
        this.setState(state => ({
          isToggleOn: !state.isToggleOn
        }));
      }
    
      render() {
        return (
          <button onClick={this.handleClick}>
            {this.state.isToggleOn ? 'ON' : 'OFF'}
          </button>
        );
      }
    }
    
    ReactDOM.render(
      <Toggle />,
      document.getElementById('root')
    );
    

条件渲染

  • if...else...
  • 三目运算符
  • 与运算符 &&
  • 阻止渲染,让render方法直接返回null
function WarningBanner(props) {
  if (!props.warn) {
    return null;
  }

  return (
    <div className="warning">
      Warning!
    </div>
  );
}

列表&Key

  • 使用map()遍历,

    内部无需{}

  • 列表元素设置key属性,进行唯一标识
  • 代码示例
    
    function NumberList(props) {
      const numbers = props.numbers;
      const listItems = numbers.map((number) =>
        <li key={number.toString()}>
          {number}
        </li>
      );
      return (
        <ul>{listItems}</ul>
      );
    }
    
    const numbers = [1, 2, 3, 4, 5];
    ReactDOM.render(
      <NumberList numbers={numbers} />,
      document.getElementById('root')
    );
    

表单

  • event.target.value 获取值
  • 代码示例
    
    class NameForm extends React.Component {
      constructor(props) {
        super(props);
        this.state = {value: ''};
    
        this.handleChange = this.handleChange.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
      }
    
      handleChange(event) {
        this.setState({value: event.target.value});
      }
    
      handleSubmit(event) {
        alert('提交的名字: ' + this.state.value);
        event.preventDefault();
      }
    
      render() {
        return (
          <form onSubmit={this.handleSubmit}>
            <label>
              名字:
              <input type="text" value={this.state.value} onChange={this.handleChange} />
            </label>
            <input type="submit" value="提交" />
          </form>
        );
      }
    }
    

状态提升

  • 在 React 应用中,任何可变数据应当只有一个相对应的唯一“数据源”。通常,state 都是首先添加到需要渲染数据的组件中去。然后,如果其他组件也需要这个 state,那么你可以将它提升至这些组件的最近共同父组件中。你应当依靠自上而下的数据流,而不是尝试在不同组件间同步 state。
    • !!将子组件中的共用状态,提取到最近的共同父组件中。
    • !!子组件修改父组件中的状态,调用父组件方法,或node,事件触发

组合VS继承

  • 推荐使用组合而非继承来实现组件间的代码重用。

React 哲学

高级指引

标准和指南

  • JSX 支持所有 aria-* HTML 属性

  • 使用 React Fragments 来组合各个组件或者短语法<></>

import React, { Fragment } from 'react';

function ListItem({ item }) {
  return (
    <Fragment>
      <dt>{item.term}</dt>
      <dd>{item.description}</dd>
    </Fragment>
  );
}

function ListItem({ item }) {
  return (
    <>      <dt>{item.term}</dt>
      <dd>{item.description}</dd>
    </>  );
}
  • 请注意 for 在 JSX 中应该被写作 htmlFor
<label htmlFor="namedInput">Name:</label>
<input id="namedInput" type="text" name="name"/>

代码分割

  • import()
    • 使用前
    import { add } from './math';
    
    console.log(add(16, 26));
    
    
    • 使用后
    import("./math").then(math => {
       console.log(math.add(16, 26));
    });
    
    
  • React.lazy
    • 使用前
    import OtherComponent from './OtherComponent';
    
    • 使用后
    const OtherComponent = React.lazy(() => import('./OtherComponent'));
    
  • React.Suspense
    • 可以指定加载指示器,以防其组件树中的某些子组件尚未具备渲染条件
    
    // 该组件是动态加载的
    

const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    // 显示 <Spinner> 组件直至 OtherComponent 加载完成
    <React.Suspense fallback={<Spinner />}>
      <div>
        <OtherComponent />
      </div>
    </React.Suspense>
  );
}
```
  • 异常捕获边界
  • 基于路由的代码分割
  • 命名导出

Context

  • 跳过多层嵌套的传值,Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props
  • 使用场景:主题
  • React.createContext
    • const MyContext = React.createContext(defaultValue);
    • 创建Context对象,指定默认值
    • 只有当组件所处的树中没有匹配到 Provider 时,其 defaultValue 参数才会生效
  • Context.Provider
    • <MyContext.Provider value={/* 某个值 */}>
    • Provider 接收一个 value 属性,传递给消费组件
  • Class.contextType
    • 挂载在 class 上的 contextType 属性会被重赋值为一个React.createContext() 创建的 Context 对象
    • 将context挂载在class上
    class MyClass extends React.Component {
      static contextType = MyContext;  //挂载到类上
      render() {
        let value = this.context;
        /* 基于这个值进行渲染工作 */
      }
    

} //另一种挂载方式 MyClass.contextType = MyContext; ```

  • Context.Consumer

    • React 组件也可以订阅context的变更,在函数组件中完成订阅context

        <ThemeContext.Consumer>
            {({theme, toggleTheme}) => (
              <button
                onClick={toggleTheme}
                style={{ backgroundColor: theme.background }}
              >
                Toggle Them
              </button>
            )}
          </ThemeContext.Consumer>
      
  • Context.displayName

    • context 对象接受一个名为 displayName 的 property,类型为字符串。React DevTools 使用该字符串来确定 context 要显示的内容。

      const MyContext = React.createContext(/* some value */);
      MyContext.displayName = 'MyDisplayName';
      
      <MyContext.Provider> // "MyDisplayName.Provider" 在 DevTools 中
      <MyContext.Consumer> // "MyDisplayName.Consumer" 在 DevTools 中
      
  • 示例: 修改按钮主题

    • theme-context.jsx

      import React, { Fragment } from "react";
      export const themes = {
        light: {
          foreground: "#000000",
          background: "#E91E63",
        },
        dark: {
          foreground: "#ffffff",
          background: "#03A9F4",
        },
      };
      export const ThemeContext = React.createContext({
        theme: themes.dark,
        toggleTheme: () => {},
      });
      ThemeContext.displayName="myContext"
      
    • theme-toggler-button.jsx

      import React, { Fragment } from "react";
      import { ThemeContext } from "./theme-context";
      function ThemeTogglerButton() {
        return (
          <ThemeContext.Consumer>
            {({theme, toggleTheme}) => (
              <button
                onClick={toggleTheme}
                style={{ backgroundColor: theme.background }}
              >
                Toggle Them
              </button>
            )}
          </ThemeContext.Consumer>
        );
      }
      
      export default ThemeTogglerButton;
      
      
    • app.jsx

      import React,{Fragment,Component} from "react";
      import {themes,ThemeContext} from "./theme-context";
      import ThemeTogglerButton from "./theme-toggler-button";
      
      
      class App extends 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(){
              return(
                  <ThemeContext.Provider value={this.state}>
                      <Content></Content>
                  </ThemeContext.Provider>
              )
          }
      }
      
      function Content(){
          return (
              <Fragment>
                  <ThemeTogglerButton/>
              </Fragment>
          )
      }
      export default App;
      

错误边界

  • 为了解决部分 UI 的 JavaScript 错误不应该导致整个应用崩溃

  • 错误边界是一种 React 组件,这种组件可以捕获并打印发生在其子组件树任何位置的 JavaScript 错误,并且,它会渲染出备用 UI,而不是渲染那些崩溃了的子组件树。错误边界在渲染期间、生命周期方法和整个组件树的构造函数中捕获错误。

  • 注意

    错误边界无法捕获以下场景中产生的错误:

    • 事件处理(了解更多
    • 异步代码(例如 setTimeoutrequestAnimationFrame 回调函数)
    • 服务端渲染
    • 它自身抛出来的错误(并非它的子组件)
  • 示例

    import React, { Suspense, lazy } from "react";
    
    
    class ErrorBoundary extends React.Component {
      constructor(props) {
        super(props);
        this.state = { hasError:false };
      }
    
      static getDerivedStateFromError(error){
          return {hasError:true}
      }
    
      componentDidCatch(error,errorInfo){
        // console.log(error, errorInfo);
      }
    
      render() {
       if(this.state.hasError){
            return <h1>有错误!!!</h1>
       }
       return this.props.children;
      }
    }
    
    export default ErrorBoundary;
    

    使用

    <ErrorBoundary>
      <MyWidget />
    </ErrorBoundary>
    

Refs转发

  • Ref 转发是一个可选特性,其允许某些组件接收 ref,并将其向下传递(换句话说,“转发”它)给子组件。

  • 示例

    const FancyButton = React.forwardRef((props, ref) => (
      <button ref={ref} className="FancyButton">
        {props.children}
      </button>
    ));
    
    // 你可以直接获取 DOM button 的 ref:
    const ref = React.createRef();
    <FancyButton ref={ref}>Click me!</FancyButton>;
    
    

Fragments

  • 无需添加额外节点

高阶组件

  • 高阶组件是参数为组件,返回值为新组件的函数。

  • 示例

    // 此函数接收一个组件...
    function withSubscription(WrappedComponent, selectData) {
      // ...并返回另一个组件...
      return class extends React.Component {
        constructor(props) {
          super(props);
          this.handleChange = this.handleChange.bind(this);
          this.state = {
            data: selectData(DataSource, props)
          };
        }
    
        componentDidMount() {
          // ...负责订阅相关的操作...
          DataSource.addChangeListener(this.handleChange);
        }
    
        componentWillUnmount() {
          DataSource.removeChangeListener(this.handleChange);
        }
    
        handleChange() {
          this.setState({
            data: selectData(DataSource, this.props)
          });
        }
    
        render() {
          // ... 并使用新数据渲染被包装的组件!
          // 请注意,我们可能还会传递其他属性
          return <WrappedComponent data={this.state.data} {...this.props} />;
        }
      };
    }
    
    
    • 使用
    const CommentListWithSubscription = withSubscription(
      CommentList,
      (DataSource) => DataSource.getComments()
    );
    
    const BlogPostWithSubscription = withSubscription(
      BlogPost,
      (DataSource, props) => DataSource.getBlogPost(props.id)
    );
    
  • 包装显示命名方便调试

    function withSubscription(WrappedComponent) {
      class WithSubscription extends React.Component {/* ... */}
      WithSubscription.displayName = `WithSubscription(${getDisplayName(WrappedComponent)})`;
      return WithSubscription;
    }
    
    function getDisplayName(WrappedComponent) {
      return WrappedComponent.displayName || WrappedComponent.name || 'Component';
    }
    
  • 注意

    • 不要在 render 方法中使用 HOC

    • Refs不会被传递

    • 务必复制静态方法

      • 方法一
      function enhance(WrappedComponent) {
        class Enhance extends React.Component {/*...*/}
        // 必须准确知道应该拷贝哪些方法 :(
        Enhance.staticMethod = WrappedComponent.staticMethod;
        return Enhance;
      }
      
      
      • 方法二
      import hoistNonReactStatic from 'hoist-non-react-statics';
      function enhance(WrappedComponent) {
        class Enhance extends React.Component {/*...*/}
        hoistNonReactStatic(Enhance, WrappedComponent);
        return Enhance;
      }
      
      

与第三方库协同

深入JSX

  • 用户定义的组件必须以大写字母开头

  • 在运行时选择类型

    import React from 'react';
    import { PhotoStory, VideoStory } from './stories';
    
    const components = {
      photo: PhotoStory,
      video: VideoStory
    };
    
    function Story(props) {
      // 正确!JSX 类型可以是大写字母开头的变量。
      const SpecificStory = components[props.storyType];
      return <SpecificStory story={props.story} />;
    
  • Props的默认值为true

    <MyTextBox autocomplete />
    
    <MyTextBox autocomplete={true} />
    
  • 属性展开

    function App1() {
      return <Greeting firstName="Ben" lastName="Hector" />;
    }
    
    function App2() {
      const props = {firstName: 'Ben', lastName: 'Hector'};
      return <Greeting {...props} />;
    }
    
  • 渲染 falsetruenullundefined 等值,你需要先将它们转换为字符串,否则将会忽略

    <div>
      My JavaScript variable is {String(myVariable)}.
    </div>
    

性能优化

Portals

  • 当你从组件的 render 方法返回一个元素时,该元素将被挂载到 DOM 节点中离其最近的父节点

    ReactDOM.createPortal(child, container)
    
  • 使用场景: 一个 portal 的典型用例是当父组件有 overflow: hiddenz-index 样式时,但你需要子组件能够在视觉上“跳出”其容器。例如,对话框、悬浮卡以及提示框

Profiler

  • 测试组件渲染速度,便于优化

  • 用法

    render(
      <App>
        <Profiler id="Navigation" onRender={callback}>
          <Navigation {...props} />
        </Profiler>
        <Main {...props} />
      </App>
    );
    
  • onRender回调

    function onRenderCallback(
      id, // 发生提交的 Profiler 树的 “id”
      phase, // "mount" (如果组件树刚加载) 或者 "update" (如果它重渲染了)之一
      actualDuration, // 本次更新 committed 花费的渲染时间
      baseDuration, // 估计不使用 memoization 的情况下渲染整颗子树需要的时间
      startTime, // 本次更新中 React 开始渲染的时间
      commitTime, // 本次更新中 React committed 的时间
      interactions // 属于本次更新的 interactions 的集合
    ) {
      // 合计或记录渲染时间。。。
    }
    

不使用 ES6

不使用 JSX

协调

Refs & DOM

  • 创建Refs

    class MyComponent extends React.Component {
      constructor(props) {
        super(props);
        this.myRef = React.createRef();
      }
      render() {
        return <div ref={this.myRef} />;
      }
    }
    
  • 访问Refs

    const node = this.myRef.current;
    

Render Props

  • 解决横切关注点

  • render prop 是一个用于告知组件需要渲染什么内容的函数 prop。

  • 示例

    class Cat extends React.Component {
      render() {
        const mouse = this.props.mouse;
        return (
          <img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
        );
      }
    }
    
    class Mouse extends React.Component {
      constructor(props) {
        super(props);
        this.handleMouseMove = this.handleMouseMove.bind(this);
        this.state = { x: 0, y: 0 };
      }
    
      handleMouseMove(event) {
        this.setState({
          x: event.clientX,
          y: event.clientY
        });
      }
    
      render() {
        return (
          <div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>
    
            {/*
              Instead of providing a static representation of what <Mouse> renders,
              use the `render` prop to dynamically determine what to render.
            */}
            {this.props.render(this.state)}
          </div>
        );
      }
    }
    
    class MouseTracker extends React.Component {
      render() {
        return (
          <div>
            <h1>移动鼠标!</h1>
            <Mouse render={mouse => (
              <Cat mouse={mouse} />
            )}/>
          </div>
        );
      }
    }
    

静态类型检查

  • Flow : 是一个针对 JavaScript 代码的静态类型检测器

    • 在项目中添加

      yarn add --dev flow-bin || npm install --save-dev flow-bin

    • 配置: 将 flow 添加到项目 package.json"scripts" 部分

      {
        // ...
        "scripts": {
          "flow": "flow",
          // ...
        },
        // ...
      }
      
    • 运行

      yarn run flow init ||npm run flow init

  • TypeScript

    • 需要创建使用 TypeScript 的新项目

      • npx create-react-app my-app --template typescript
    • 添加TypeScript到现有项目中

      • yarn add --dev typescript||npm install --save-dev typescript

      • tsc 添加到 package.json 中的 “scripts” 部分

        {
          // ...
          "scripts": {
            "build": "tsc",
            // ...
          },
          // ...
        }
        
        
      • 配置TypeScrpt 编辑器

        yarn run tsc --init || npx tsc --init

      • 目录整理

        ├── package.json
        ├── src
        │   └── index.ts
        └── tsconfig.json
        
      • 其次,我们将通过配置项告诉编译器源码和输出的位置。

        {
          "compilerOptions": {
            // ...
            "rootDir": "src",
            "outDir": "build"
            // ...
          },
        }
        
        
      • 运行TypeScript yarn build||npm run build

      • 类型定义

        yarn add --dev @types/react || npm i --save-dev @types/react

      • 局部声明 :项目根目录下创建declarations.d.ts

        
        declare module 'querystring' {
          export function stringify(val: object): string
          export function parse(val: string): object
        }
        

严格模式

使用 PropTypes 类型检查

非受控组件

  • ref 实现

  • 默认值: defaultValue,defaultChecked

  • 示例

    class FileInput extends React.Component {
      constructor(props) {
        super(props);
        this.handleSubmit = this.handleSubmit.bind(this);
        this.fileInput = React.createRef();
      }
      handleSubmit(event) {
        event.preventDefault();
        alert(
          `Selected file - ${this.fileInput.current.files[0].name}`
        );
      }
    
      render() {
        return (
          <form onSubmit={this.handleSubmit}>
            <label>
              Upload file:
              <input type="file" ref={this.fileInput} />
            </label>
            <br />
            <button type="submit">Submit</button>
          </form>
        );
      }
    }
    
    ReactDOM.render(
      <FileInput />,
      document.getElementById('root')
    );
    
    

Web Components