Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React #3

Open
sfsoul opened this issue Aug 31, 2020 · 10 comments
Open

React #3

sfsoul opened this issue Aug 31, 2020 · 10 comments

Comments

@sfsoul
Copy link
Owner

sfsoul commented Aug 31, 2020

Props 的只读性

组件无论是使用函数声明还是通过 class 声明,都决不能修改自身的 props。所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。

正确地使用 State

不要直接修改 State

// 此代码不会重新渲染组件
this.state.comment = 'Hello';

应该使用 setState()

this.setState({
  comment: 'Hello'
})

State 的更新可能是异步的

出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用。 因为 this.propsthis.state 可能会异步更新,所以不要依赖它们的值来更新下一个状态。

// 此代码无法更新计数器:
this.setState({
  counter: this.state.counter + this.props.increment
});

要解决这个问题,setState() 接收一个函数而不是一个对象。此函数用上一个 state 作为第一个参数,将此次更新被应用时的 props 作为第二个参数:

this.setState((state, props) => ({
  counter: state.counter + props.increment
}))

State 的更新会被合并

调用 setState() 时,React 会把你提供的对象合并到当前的 state

向事件处理程序传递参数

在循环中通常会为事件处理函数传递额外的参数。

// 两种方式都可以向事件处理函数传递参数:
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

在这两种情况下,React 的事件对象 e 会被作为第二个参数传递。若通过箭头函数的方式,事件对象必须显式的进行传递;而通过 bind 的方式,事件对象以及更多的参数将会被隐式的进行传递。

阻止组件渲染

希望能隐藏组件,即使它已经被其他组件渲染。可以让 render 方法直接返回 null,而不进行任何渲染。在组件的 render 方法中返回 null 并不会影响组件的生命周期。componentDidUpdate 依然会被调用。

function WarningBanner(props) {
  if (!props.warn) {
    return null;
  }
  return (
    <div className="warning">
      Warning!
    </div>
  )
}

用 key 提取组件

元素的 key 只有放在就近的数组上下文中才有意义。

一个好的经验法则是:在 map() 方法中的元素需要设置 key 属性。

状态提升

在 React 应用中,任何可变数据应当只有一个相对应的唯一 "数据源"。通常,state 都是首先添加到需要渲染数据的组件中去。然后如果其他组件也需要这个 state,可以将它提升至这些组件的最近共同父组件中。应当依靠自上而下的数据流,而不是尝试在不同组件间同步 state。

@sfsoul
Copy link
Owner Author

sfsoul commented Sep 7, 2020

代码分割

React.lazy

异常捕获边界(Error boundaries)

命名导出

@sfsoul
Copy link
Owner Author

sfsoul commented Sep 8, 2020

React 中如何添加多个className

@sfsoul
Copy link
Owner Author

sfsoul commented Sep 8, 2020

错误边界(Error Boundaries)

关于事件处理器

错误边界无法捕获事件处理器内部的错误。与 render 方法和生命周期方法不同,事件处理器不会在渲染期间触发。因此若它们抛出异常,React 仍然能够知道需要在屏幕上显示什么。若需要在事件处理器内部捕获错误,使用 JS 的 try/catch 语句。

@sfsoul
Copy link
Owner Author

sfsoul commented Sep 14, 2020

为什么需要在 React 类组件中为事件处理程序绑定 this

@sfsoul
Copy link
Owner Author

sfsoul commented Sep 14, 2020

函数组件与 class 组件的区别?

@sfsoul
Copy link
Owner Author

sfsoul commented Sep 14, 2020

无状态组件和有状态组件区别?

无状态组件

无状态组件(Stateless Component)是最基础的组件形式,由于没有状态的影响所以就是纯静态展示的作用。它的基本组成结构就是属性(props)加上一个回调函数。由于不涉及到状态的更新,所以这种组件的复用性也最强。

export const Header = (props) => {
  return (
    <div>无状态组件</div>
  )
}

有状态组件

在无状态组件的基础上,若组件内部包含状态(state)且状态会随着事件或者外部的消息而发生改变的时候,就构成了有状态组件(Stateful Component)。有状态组件通常会带有生命周期(lifeCycle),用以在不同的时刻触发状态的更新。

export class Home extends Component {
    constructor(props) {
        super(props);
    }
    render() {
        return (
            <Header />
        )
    }
}

@sfsoul
Copy link
Owner Author

sfsoul commented Sep 15, 2020

@sfsoul
Copy link
Owner Author

sfsoul commented Sep 15, 2020

Refs and the DOM

Refs 提供了一种方式,允许访问DOM节点或在 render 方法中创建的 React 元素。

何时使用 Refs

  • 管理焦点,文本选择或媒体播放;
  • 触发强制动画;
  • 集成第三方 DOM 库

创建 Refs

Refs 使用 React.createRef() 创建的,并通过 ref 属性附加到 React 元素。在构造组件时,通常将 Refs 分配给实例属性,以便在整个组件中引用它们。

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

访问 Refs

当 ref 被传递给 render 中的元素时,对该节点的引用可以在 ref 的 current 属性中访问。

const node = this.myRef.current;

ref 的值根据节点的类型而有所不同:

  • ref 属性用于 HTML 元素时,构造函数中使用 React.createRef() 创建的 ref 接收底层 DOM 元素作为其 current 属性;
  • ref 属性用于自定义 class 组件时,ref 对线下接收组件的挂载实例作为其 current 属性;
  • 不能在函数组件上使用 ref 属性,因为它们没有实例。

Refs 与 函数组件

默认情况下,不能在函数组件上使用 ref 属性,因为它们没有实例:

function MyFunctionComponent() {
  return <input />;
}

class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.textInput = React.createRef();
  }
  render() {
    // This will *not* work!
    return (
      <MyFunctionComponent ref={this.textInput} />
    );
  }
}

可以在函数组件内部使用 ref 属性,只要它指向一个 DOM 元素或 class 组件:

function CustomTextInput(props) {
  // 这里必须声明 textInput,这样 ref 才可以引用它
  const textInput = useRef(null);

  function handleClick() {
    textInput.current.focus();
  }

  return (
    <div>
      <input
        type="text"
        ref={textInput} />
      <input
        type="button"
        value="Focus the text input"
        onClick={handleClick}
      />
    </div>
  );
}

@sfsoul
Copy link
Owner Author

sfsoul commented Sep 15, 2020

组件的生命周期

挂载

当组件实例被创建并插入 DOM 中时,其生命周期调用顺序如下:

  • constructor
  • render()
  • componentDidMount()

更新

当组件的 props 或 state 发生变化时会触发更新。组件更新的生命周期调用顺序如下:

  • shouldComponentUpdate()
  • render()
  • componentDidUpdate()

卸载

当组件从 DOM 中移除时会调用:

  • componentWillUnmount()

常用的生命周期方法

render()

render() 方法是 class 组件中唯一必须实现的方法。

render() 函数应该为纯函数,意味着在不修改组件 state 的情况下,每次调用时都返回相同的结果,并且它不会直接与浏览器交互。如需与浏览器进行交互,请在 componentDidMount() 或其他生命周期方法中执行你的操作。保存 render() 为纯函数,可以使组件更容易思考。若 shouldComponentUpdate() 返回 false,则不会调用 render()

constructor()

constructor 函数中 不要调用 setState() 方法。若组件需要使用内部 state,直接在构造函数中为 this.state 赋值初始 state

constructor(props) {
  super(props);
  // 不要在这里调用 this.setState()
  this.state = { counter: 0 };
  this.handleClick = this.handleClick.bind(this);
}

避免将 props 的值复制给 state,这是个常见错误:

constructor(props) {
 super(props);
 // 不要这样做
 this.state = { color: props.color };
}

componentDidMount()

componentDidMount() 会在组件挂载后(插入 DOM 树中)立即调用。依赖于 DOM 节点的初始化应该放在这里。如需通过网络请求获取数据,此处是实例化请求的好地方。此方法是比较适合添加订阅的地方,若添加了订阅,请不要忘记在 componentWillUnmount() 里取消订阅。可以在 componentDIdMount() 里直接调用 setState()。它将触发额外渲染,但此渲染会发生在浏览器更新屏幕之前。如此保证了即使在 render() 两次调用的情况下,用户也不会看到中间状态。

componentDidUpdate(prevProps, prevState, snapshot)

componentDidUpdate 会在更新后被立即调用。首次渲染不会执行此方法。当组件更新后,可以在此处对 DOM 进行操作。若对更新前后的 props 进行了比较,也可以选择在此处进行网络请求(当 props 未发生变化时,则不会执行网络请求)。

componentDidUpdate(prevProps) {
  // 典型用法(不要忘记比较 props):
  if (this.props.userID !== prevProps.userID) {
    this.fetchData(this.props.userID);
  }
}

可在 componentDidUpdate() 中直接调用 setState(),但请注意它必须被包裹在一个条件语句里,否则会导致死循环。它会导致额外的重新渲染,影响组件性能。

componentWillUnmount()

componentWillUnmount() 会在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作,例如:清除 timer,取消网络请求或清除在 componentDidMount 中创建的订阅等。componentWillUnmount 中不应该调用 setState(),因为该组件将永远不会重新渲染。组件实例卸载后,将永远不会再挂载它。

setState(updater, [callback])

setState() 将对组件 state 的更改排入队列,并通知 React 需要使用更新后的 state 重新渲染此组件及其子组件。将 setState() 视为请求而不是立即更新组件的命令。为了更好的感知性能,React 会延迟调用它,然后通过一次传递更新多个组件。React 并不会保证 state 的变更会立即生效。setState() 并不总是立即更新组件。它会批量推迟更新。这使得在调用 setState() 后立即读取 this.state 成为了隐患。为了消除隐患,请使用 componentDidUpdate 或者 setState 的回调函数(setState(updater, callback)),这两种方式都可以保证在应用更新后触发。

forceUpdate()

默认情况下,当组件的 stateprops 发生变化时,组件将重新渲染。render() 方法依赖于其他数据,则可以调用 forceUpdate() 强制让组件重新渲染。调用 forceUpdate() 将致使组件调用 render() 方法,此操作会跳过该组件的 shouldComponentUpdate()。但其子组件会触发正常的生命周期方法,包括 shouldComponentUpdate() 方法。若标记发生变化,React 仍将只更新 DOM。

@sfsoul
Copy link
Owner Author

sfsoul commented Sep 16, 2020

受控组件 vs 非受控组件

非受控组件

表单数据将交由 DOM 节点来处理。

要编写一个非受控组件,而不是为每个状态更新都编写数据处理函数,可以使用 ref 来从 DOM 节点中获取表单数据。

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.handleSubmit = this.handleSubmit.bind(this);
    this.input = React.createRef();
  }

  handleSubmit(event) {
    alert('A name was submitted: ' + this.input.current.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input type="text" ref={this.input} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

默认值

通过指定一个 defaultValue 属性来赋予组件一个初始值。

render() {
  return (
    <form onSubmit={this.handleSubmit}>
      <label>
        Name:
        <input
          defaultValue="Bob"
          type="text"
          ref={this.input} />
      </label>
      <input type="submit" value="Submit" />
    </form>
  );
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant