React patterns - Phần 1

Đây là những react patterns được tổng hợp tại trang https://reactpatterns.com/, rất hữu ích cho những ai mới tiếp cận với React. Khá nhiều nên mình sẽ tách thành 2 bài viết.

Stateless function

Stateless functions thường dùng để viết các components nhỏ có tính tái sử dụng cao. Chúng không có state, chúng chỉ là những functions.

const Greeting = () => <div>Hi there!</div>

Có thể truyền vào propscontext.

const Greeting = (props, context) =>
  <div style={{color: context.color}}>Hi {props.name}!</div>

Có thể định nghĩa các biến local sử dụng trong function.

const Greeting = (props, context) => {
  const style = {
    fontWeight: "bold",
    color: context.color,
  }

  return <div style={style}>{props.name}</div>
}

Bạn cũng có thể nhận được kết quả tương tự bằng cách sử dụng những functions khác.

const getStyle = context => ({
  fontWeight: "bold",
  color: context.color,
})

const Greeting = (props, context) =>
  <div style={getStyle(context)}>{props.name}</div>

Cũng có thể định nghĩa defaultProps, propTypescontextTypes như các React components.

Greeting.propTypes = {
  name: PropTypes.string.isRequired
}
Greeting.defaultProps = {
  name: "Guest"
}
Greeting.contextTypes = {
  color: PropTypes.string
}

JSX spread attributes

Spread Attributes là một tính năng của JSX. Nó là cú pháp để truyền tất cả các properties của một object như là JSX attributes.

Hai ví dụ này là tương đương nhau.

// props truyền vào bằng JSX attributes
<main className="main" role="main">{children}</main>

// props được "spread"  từ object
<main {...{className: "main", role: "main", children}} />

Sử dụng cách này để truyền toàn bộ props vào các components.

const FancyDiv = props =>
  <div className="fancy" {...props} />

Bây giờ, chúng ta thêm các props cho FancyDiv dưới dạng attributes.

<FancyDiv data-id="my-fancy-div">So Fancy</FancyDiv>

// Đây là output: <div className="fancy" data-id="my-fancy-div">So Fancy</div>

Hãy nhớ rằng theo thứ tự ưu tiên, nếu props.classNameđược định nghĩa, nó sẽ ghi đè className được định nghĩa bởi FancyDiv

<FancyDiv className="my-fancy-div" />

// Đây là output: <div className="my-fancy-div"></div>

Chúng ta có thể khiến cho className của FancyDiv luôn luôn được ưu tiên bằng cách đặt nó sau props ({...props}).

//  `className` sẽ luôn ghi đè `className` từ props
const FancyDiv = props =>
  <div {...props} className="fancy" />

Đôi khi bạn muốn giữ cả 2 className vì chỉ muốn custom lại style chẳng hạn. Bạn có thể gộp lại chúng như sau.

const FancyDiv = ({ className, ...props }) =>
  <div
    className={["fancy", className].join(' ')}
    {...props}
  />

destructuring arguments

Destructuring assignment là một tính năng của ES2015. Nó kết hợp tuyệt vời với props trong các Stateless Functions.

These examples are equivalent.

const Greeting = props => <div>Hi {props.name}!</div>

const Greeting = ({ name }) => <div>Hi {name}!</div>

Cú pháp sử dụng rest parameter (...) cho phép bạn thu thập tất cả những properties còn lại trong một object.

const Greeting = ({ name, ...props }) =>
  <div>Hi {name}!</div>

Chúng ta cũng có thể dùng JSX Spread Attributes để truyền toàn bộ props tới component.

const Greeting = ({ name, ...props }) =>
  <div {...props}>Hi {name}!</div>

Để tránh truyền các props không phải DOM tới các components. Destructuring làm cho việc này trở nên dễ dàng bởi vì bạn có thể tạo props object mới không bao gồm các props đã được chỉ định.

conditional rendering

Bạn không thể sử dụng các điều kiện if/else thông thường bên trọng định nghĩa của component. Toán tử điều kiện conditional (ternary) operator là cách làm phù hợp.

if

{condition && <span>Render khi `true`</span> }

unless

{condition || <span>Render khi `false`</span> }

if-else (Ít code, chỉ 1 dòng)

{condition
  ? <span>Render khi `true`</span>
  : <span>Render khi `false`</span>
}

if-else (Nhiều code, nhiều dòng)

{condition ? (
  <span>
    Rendered when `truthy`
  </span>
) : (
  <span>
    Rendered when `falsey`
  </span>
)}

Children types

React có thể render children thuộc nhiều types. Trong hầu hết các trường hợp là một array hoặc một string.

string

<div>
  Hello World!
</div>

array

<div>
  {["Hello ", <span>World</span>, "!"]}
</div>

Functions cũng có thể sử dụng như children. Tuy nhiên, nó đòi hỏi việc kết hợp với parent component phải hiệu quả.

function

<div>
  {(() => { return "hello world!"})()}
</div>

Children dạng array

Cung cấp một array như children là rất phổ biết. Nó là cách các list được render trong React.

Chúng ta sử dụng map() để tạo một mảng React Elements cho mỗi giá trị trong mảng.

<ul>
  {["first", "second"].map((item) => (
    <li>{item}</li>
  ))}
</ul>

Điều đó tương đương với việc cung cấp một array thông thường.

<ul>
  {[
    <li>first</li>,
    <li>second</li>,
  ]}
</ul>

Pattern này có thể kết hợp với destructuring, JSX Spread Attributes, và các components khác.

<ul>
  {arrayOfMessageObjects.map(({ id, ...message }) =>
    <Message key={id} {...message} />
  )}
</ul>

Children dạng function

Sử dụng function như là children vốn không hữu ích lắm.

<div>{() => { return "hello world!"}()}</div>

Tuy nhiên, nó có thể được sử dụng trong một số components trong một số trường hợp đặc biệt. Kỹ thuật này thường được gọi là render callbacks.

Đây là một kỹ thuật mãnh mẽ được sử dụng bởi các thư viện ReactMotion. Khi được áp dụng, rendering logic có thể được chứa trong owner component, thay vì được được uỷ quyền.

Xem Render callbacks trong bài viết này để biết thêm.

Render callback

Đây là một component sử dụng một Render callback. Nó không hữu ích, nhưng đó là một minh hoạ để bắt đầu

const Width = ({ children }) => children(500)

Component gọi children như một function, với một số arguments. Ở đây chúng ta truyền vào 500.

Để sử dụng component này, chúng ta cần truyền vào nó một children kiểu function.

<Width>
  {width => <div>window is {width}</div>}
</Width>

Chúng ta sẽ nhận được output.

<div>window is 500</div>

Với thiết lập này, chúng ta có thể sử dụng width này để đưa ra quyết định render.

<Width>
  {width =>
    width > 600
      ? <div>min-width đáp ứng yêu cầu!</div>
      : null
  }
</Width>

Nếu chúng ta có kế hoạch sử dụng condition này nhiều lần, Chúng ta có thể định nghĩa các components khác để gói gọn những logic được tái sử dụng.

const MinWidth = ({ width: minWidth, children }) =>
  <Width>
    {width =>
      width > minWidth
        ? children
        : null
    }
  </Width>

Rõ ràng là một static Width component là không có ý nghĩa lắm nhưng một component theo dõi sự thay đổi kích thước của màn hình đôi khi cũng được sử dụng. Đây là ví dụ:

class WindowWidth extends React.Component {
  constructor() {
    super()
    this.state = { width: 0 }
  }

  componentDidMount() {
    this.setState(
      {width: window.innerWidth},
      window.addEventListener(
        "resize",
        ({ target }) =>
          this.setState({width: target.innerWidth})
      )
    )
  }

  render() {
    return this.props.children(this.state.width)
  }
}

Phần 2

Bạn có thể đọc phần 2 của loạt bài viết React patterns tại đây: React patterns - Phần 2.