电子版: React 模式

一份关于 react 的学习资料
更新于: 2023-09-01 10:08:05

函数组件

函数组件 是最简单的一种声明可复用组件的方法

特色代码
基本Greeting
function Greeting() {
  return <div>Hi there!</div>;
}
从第一个形参中获取属性集 (props)
function Greeting(props) {
  return <div>Hi {props.name}!</div>;
}
按自己的需要可以在函数组件中定义任意变量
function Greeting(props) {
  let style = {
    fontWeight: "bold",
    color: context.color
  };

  return <div style={style}>Hi {props.name}!</div>;
}
使用 defaultProps 为任意必有属性设置默认值
function Greeting(props) {
  return <div>Hi {props.name}!</div>;
}

Greeting.defaultProps = {
  name: "Guest"
};

属性解构 (Destructuring props)

特性代码
基本语法
// 好比字面量赋值的反转形式。
let person = { name: "chantastic" };
let { name } = person;

// 同样适用于数组。
let things = ["one", "two"];
let [first, second] = things;
解构赋值被用在很多 函数组件
function Greeting(props) {
  return <div>Hi {props.name}!</div>;
}

function Greeting({ name }) {
  return <div>Hi {name}!</div>;
}
 剩余参数(Rest)
function Greeting({ name, ...restProps }) {
  return <div>Hi {name}!</div>;
}

JSX 中的属性展开 (JSX spread attributes)

它是一种语法,专门用来把对象上的属性转换成 JSX 中的属性

特性代码
我们可以 扩散 restProps 对象的所有属性到 div 元素上
function Greeting({ name, ...restProps }) {
  return <div {...restProps}>Hi {name}!</div>;
}
这让 Gretting 组件变得非常灵活,我们可以通过传给 Gretting 组件 DOM 属性并确定这些属性一定会被传到 div 上
<Greeting name="Fancy pants" className="fancy-greeting" id="user-greeting" />

合并解构属性和其它值 

组件就是一种抽象,好的抽象是可以扩展的。

特性代码
比如说下面这个组件使用 class 属性来给按钮添加样式
function MyButton(props) {
  return <button className="btn" {...props} />;
}

<MyButton className="delete-btn">Delete...</MyButton>
JSX 中的属性展开 对先后顺序是敏感的
// 扩散属性中的 className 会覆盖组件上的 className。
// 我们可以改变它两的顺序,但是目前来说 className 只有 btn。

function MyButton(props) {
  return <button {...props} className="btn" />;
}
扩展上面的情况
// 实际中,我们可以用 classnames 这个库来完成下面的逻辑
function MyButton({ className = "", ...props }) {
  let classNames = ["btn", className].join(" ");

  return <button className={classNames} {...props} />;
}

条件渲染 (Conditional rendering)

  • 不可以在一个组件声明中使用 if/else 语句 You can't use if/else statements inside a component declarations.
  • 所以可以使用 条件(三元)运算符 和 短路计算。
特性代码
如果(if)
{
  condition && <span>Rendered when `truthy`</span>;
}
除非(else),其实就是 unless
{
  condition || <span>Rendered when `falsy`</span>;
}
如果-否则
{
  condition ? (
    <span>Rendered when `truthy`</span>
  ) : (
    <span>Rendered when `falsy`</span>
  );
}

子元素类型 (Children types)

特性代码
字符串 String
<div>Hello World!</div>
数组 Array
<div>{["Hello ", <span>World</span>, "!"]}</div>
我们使用 map() 方法创建一个新的 React 元素数组
// 方式1
<ul>
  {["first", "second"].map(item => (
    <li>{item}</li>
  ))}
</ul>

// 等价形式
<ul>{[<li>first</li>, <li>second</li>]}</ul>
联合解构、JSX 属性扩散以及其它组件一起使用,简洁无比
<ul>
  {arrayOfMessageObjects.map(({ id, ...message }) => (
    <Message key={id} {...message} />
  ))}
</ul>
函数做为子元素 (Function as children)
React 组件不支持函数类型的子元素。
然而 渲染属性 是一种可以创建组件并以函数作为子元素的模式。
渲染属性 (Render prop)
// 字义组件
const Width = ({ children }) => children(500);
// 使用组件
<Width>{width => <div>window is {width}</div>}</Width>
// 渲染策略
<Width>
  {width => (width > 600 ? <div>min-width requirement met!</div> : null)}
</Width>

代理组件 (Proxy component)

(我并不确定这个名字的准确叫法 译:代理、中介、装饰?)

特性代码
将一些属性进行封装
按钮在 web 应用中随处可见。并且所有的按钮都需要一个 type="button" 的属性。

<button type="button">
重复的写这些属性很容易出错。我们可以写一个高层组件来代理 props 到底层组件。

const Button = props =>
  <button type="button" {...props}>
样式组件 (Style component)
import classnames from "classnames";

const PrimaryBtn = props => <Btn {...props} primary />;

const Btn = ({ className, primary, ...props }) => (
  <button
    type="button"
    className={classnames("btn", primary && "btn-primary", className)}
    {...props}
  />
);
样式组件-几种形式
<PrimaryBtn />
<Btn primary />
<button type="button" className="btn btn-primary" />

布局组件 (Layout component)

布局组件表现为一些静态 DOM 元素的形式。它们一般并不需要经常更新。

特性代码
就像下面的这个组件一样,两边各自渲染了一个 children。
<HorizontalSplit
  leftSide={<SomeSmartComponent />}
  rightSide={<AnotherSmartComponent />}
/>
HorizontalSplit 组件是两个子组件的父元素,我们可以告诉组件永远都不要更新
class HorizontalSplit extends React.Component {
  shouldComponentUpdate() {
    return false;
  }

  render() {
    <FlexContainer>
      <div>{this.props.leftSide}</div>
      <div>{this.props.rightSide}</div>
    </FlexContainer>
  }
}

容器组件 (Container component)

「容器用来获取数据然后渲染到子组件上,仅仅如此。」

特性代码
静态数据
const CommentList = ({ comments }) => (
  <ul>
    {comments.map(comment => (
      <li>
        {comment.body}-{comment.author}
      </li>
    ))}
  </ul>
);
自带请求的数据组件
class CommentListContainer extends React.Component {
  constructor() {
    super()
    this.state = { comments: [] }
  }

  componentDidMount() {
    $.ajax({
      url: "/my-comments.json",
      dataType: 'json',
      success: comments =>
        this.setState({comments: comments});
    })
  }

  render() {
    return <CommentList comments={this.state.comments} />
  }
}

高阶组件 (Higher-order component)

  • 高阶函数 是至少满足下列一个条件的函数:
    • 接受一个或多个函数作为输入
    • 输出一个函数
特性代码
其实就一个函数,能返回一个组件的函数
const Connect = ComposedComponent =>
  class extends React.Component {
    constructor() {
      super();
      this.state = { name: "" };
    }

    componentDidMount() {
      // this would fetch or connect to a store
      this.setState({ name: "Michael" });
    }

    render() {
      return <ComposedComponent {...this.props} name={this.state.name} />;
    }
  };

状态提升 (State hoisting)

  1. 函数组件 没有状态 (就像名字暗示的一样)。
  2. 事件是状态的变化。
  3. 它们的数据需要传递给状态化的父 容器组件
  4. 这就是所谓的「状态提升」
这个状态 被提升 到了容器中,通过添加回调函数,回调中可以更新本地状态。这就设置了一个很清晰边界,并且使功能组件的可重用性最大化。
这个模式并不限于函数组件。因为函数组件没有生命周期事件,你也可以在类组件中使用这种模式。
受控输入 是一种与状态提升同时使用时很重要的模式
特性代码
它是通过将回调从容器组件传递给子组件来完成的
class NameContainer extends React.Component {
  constructor() {
    super();
    this.state = { name: "" };
  }

  render() {
    return <Name onChange={newName => this.setState({ name: newName })} />;
  }
}

const Name = ({ onChange }) => (
  <input onChange={e => onChange(e.target.value)} />
);

受控输入 (Controlled input)

  • 受控组件Controlled Components: 在受控组件中,组件的状态(例如input的值)由React的state管理。每当用户与表单元素交互时,onChange等事件处理函数会更新组件的状态,从而使UI与状态保持同步。这样的方式让React完全掌控组件的状态,并且使得状态的变化可以追踪和操作。
  • 非受控组件Uncontrolled Components: 非受控组件中,表单元素的值不受React的state控制,而是由DOM自身维护。通常通过使用ref来获取表单元素的值,这种方式比较适合一些简单的交互,但在复杂情况下可能会导致代码难以维护和调试。
特性代码
一个不受控的(通常)输入
<input type="text" />
<input type="text" defaultValue="1231" />
受控的情况
class ControlledNameInput extends React.Component {
  constructor() {
    super();
    this.state = { name: "" };
  }

  render() {
    return (
      <input
        value={this.state.name}
        onChange={(e) => this.setState({ name: e.target.value })}
      />
    );
  }
}
受控组件(Controlled Components)
import React, { Component } from 'react';

class ControlledComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      inputValue: ''
    };
  }

  handleInputChange = (event) => {
    this.setState({ inputValue: event.target.value });
  };

  render() {
    return (
      <div>
        <input
          type="text"
          value={this.state.inputValue}
          onChange={this.handleInputChange}
        />
        <p>Input Value: {this.state.inputValue}</p>
      </div>
    );
  }
}

export default ControlledComponent;
非受控组件(Uncontrolled Components)
import React, { Component } from 'react';

class UncontrolledComponent extends Component {
  constructor(props) {
    super(props);
    this.inputRef = React.createRef();
  }

  handleButtonClick = () => {
    console.log('Input Value:', this.inputRef.current.value);
  };

  render() {
    return (
      <div>
        <input type="text" ref={this.inputRef} />
        <button onClick={this.handleButtonClick}>Get Input Value</button>
      </div>
    );
  }
}

export default UncontrolledComponent;
非受控组件的 value 初始化
import React, { Component } from 'react';

class UncontrolledComponentWithInitialValue extends Component {
  constructor(props) {
    super(props);
    this.inputRef = React.createRef();
  }

  handleButtonClick = () => {
    console.log('Input Value:', this.inputRef.current.value);
  };

  render() {
    return (
      <div>
        <input type="text" defaultValue="Initial Value" ref={this.inputRef} />
        <button onClick={this.handleButtonClick}>Get Input Value</button>
      </div>
    );
  }
}

export default UncontrolledComponentWithInitialValue;

关于受控与非受控组件

总结来说,受控组件提供了更强大的状态管理和交互控制,适用于需要更精细控制的情况。
而非受控组件则可以在一些简单场景下减少代码量,但可能会失去React状态管理的优势。选择使用哪种方式取决于项目的需求和开发者的偏好。

参考