React in patterns

发布时间 2023-10-06 23:23:41作者: Felix_Openmind

In brief

===> Foundation 

1. Communication
  - Input
  - Output

2. Event handlers

3. Composition
  - Using React's children API
  - Passing a child as a prop
  - Higher-order component
  - Function as a children, render prop

4. Controlled and uncontrolled inputs

5. Presentational and container components

===> Data flow

1. One direaction data flow

2. Flux (n. 不断变化的、波动、流出、溢出
  - Flux architecture and its main characteristics
  - Implementing a Flux architecture
  
3. Redux
  - Redux architecture and its main characteristics
  - Simple counter app using Redux

4. Good to know
  - Dependency injection
    * Using React's context(prior v16.3 and above)
    * Using the module system
  - Styling
    * The good old CSS class
    * Inline styling
    * CSS modules
    * Styled-components
- Integration of third-party libraries
- React and separation of concerns

Communication (组件通信)

Every React component is like a small system that operates on its own. It has its own state, input and output.
In the following section we will explore these characteristics.

Input

The input of a React component is its props. That's how we pass data to it.

// The 'Title' component has only one input => text(Prop)
function Title(props) {
  return <h1>{props.text}</h1>;
}

// 设置props属性类型
// define the type of every property
Title.propTypes = {
    text: PropTypes.string
}
// 设置props属性默认值
// defaultProps use it to set a default value of component's props
Title.defaultProps = {
    text: 'Hello World'
}
function App() {
  return <Title text='Hello React'/>;
}

// React is not defining strictly what should be passed as a prop.
// It may be whatever we want.

function SomethingElse({answer}){
  return <div> The answer is { answer } </div>
}

function Answer() {
  return <span>42</span>;
}

// later somewhere in our application
<SomethingElse answer={<Answer/>}/>

Output

The first obvious output of a React component is the rendered HTML.
However, because prop may be everything including a function we could also
send out data or trigger a process.

// Accept the user's input and sends it out to 'NameField' component
class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = { name : ''}
  }
  
  render() {
    return (
      <div>
        <NameField
          valueUpdated={name => this.setState({name})}/>
          Name: {this.state.name}
      </div>
    )
  }
}

we have an external resource that needs to be fetched on a specific page.

class ResultPage extends React.Component {
  componentDidMount() {
    this.props.getResults();
  }
  render() {
    if(this.props.results) {
      return <List results={this.props.results}/>;
    } else {
      return <LoadingScreen/>
    }
  }
}

Final thoughts: Easy to abstract and easy to compose.

Event handlers

React provides a series of attributes for handling events.
The solution is almost the same as the one used in the standard DOM.
There are some differences like using camel case or the fact that we pass a function
but overall it pretty similar

const theLogoIsClicked = () => alert('Clicked');

<Logo onClick={ theLogoIsClicked } />;
<input type='text' onChange={ event => theInputIsChanged(event.target.value)}/>
class Switcher extends React.Component {
  constructor(props)  {
    super(props);
    this.state = { name: 'React in patterns' };
  }
  render() {
    return (
      <button onClick={ this._handleButtonClick }>
        click me
      </button>
    );
  }
  
  _handleButtonClick() {
    console.log(`Button is clicked inside ${this.state.name}`);
  }
}

Composition

One of the biggest benefits of React is composability. React最大的好处在于它的组合性

  • Let's get a simple example.
// <App> -> <Header> -> <Navigation>

// ⚠ Bad example 
// Navigation.jsx
export default function Navigation() {
  return (<nav>...</nav>)
}

// Header.jsx
import Navigation from './Navigation.jsx'
export default function Header() {
  return (<header><Navigation/></Header>);
}
// app.jsx
import Header from './Header.jsx'
export default function App() {
  return (<Header/>);
}

// Good example
// using React's children API
// In React we have handy children prop.
// That's how the parent reads/accesses its children.
// This API will make our Header agnostic and dependency-free;

export default function App() {
  return (
    <Header>
      <Navigation/>
    </Header>
  );
}
// ⚠ Notice also that if we don't use { children } in Header,
// the Navigation component will never be rendered.
export default function Header({ children }) {
  return <header>{ children } </header>;
}

★★★★★ Passing a child a prop

Every React component receives props.
As we mentioned already there is no any strict rule about what
these props are, We may even pass other components.
这种用法主要用在当组件需要对子组件做出决定,但是不用关系子组件实际是什么的时候
eg: 基于特定条件隐藏子组件的可见性。

const Title = function() {
  return <h1>Hello there!</h1>;
}

const Header = function({title, children}) {
  return (
    <header>
      {title}
      {children}
    </header>
  )
}

function App() {
  return (
    <Header title={<Title/>}>
      <Navigation/>
    </Header>
  )
}

Higher-order component

  • exmaple01
var enhanceComponent = (Component) => 
  class Enhance extends React.Component {
      constructor(props) {
          super(props);
          this.state = { remoteTitle: null };
      }
      componentDidMount() {
        fetchRemoteData('path/to/endpoint').then(data => {
            this.setState({ remoteTitle: data.title });
        });
      }
      render() {
        return (
          <Component {...this.props} title={config.appTitle} remoteTitle={this.state.remoteTitle}/>
        )
      }
  }

var OriginalTitle  = ({ title, remoteTitle }) =>
  <h1>{ title }{ remoteTitle }</h1>;
var EnhancedTitle = enhanceComponent(OriginalTitle);

Function as a children, render prop

This may look weird but in fact is really powerful.

function UserName({children}) {
    return (
      <div>
        <b>{children.lastName}</b>
         {children.firstName}
      </div>
    )
}

function App() {
   const user = {
      firstName: 'Krasimir',
      lastName: 'Tsonev',
   }
   return (
      <UserName>{user}</UserName>
    )
}

  • example03

function TodoList({ todos, children }) {
  return (
    <section className='main-section'>
      <ul className='todo-list'> {
        todos.map((todo, i) => (
           <li key={ i }> {children(todo)}</li>
        ))
      }</ul>
    </section>
  )
}

function App() {
  const todos = [
    { label: 'Write tests', status:'done'},
    { label: 'Sent report', status: 'progress'},
    { label: 'Answer emails', status: 'done'},
  ]
  
  const isCompleted = todo => todo.status === 'done';
  
  return (
    <TodoList todos={ todos }>
      {
        todo => isComplete(todo) ? <b>{todo.label}</b> : todo.label;
      }
    </TodoList>
  )
}

Controlled and uncontrolled inputs

  • controlled input example
class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = { value: 'hello' };
  }
  
  render() {
    return <input type='text' value={ this.state.value }/>;
  }
}

To make the input work as expected we have to add an onChange handler
and update the state(the single source of truth). Which will trigger a new rendering cycle and we will see what typed.

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = { value: 'hello' }
    this._change = this._handleInputChange.bind(this);
  }
  render() {
    return (
      <input type='text' value={ this.state.value } onChange={ this._change }/>
    )
  }
  _handleInputChange(e) {
    this.setState({ value: e.target.value });
  }
}
  • example: uncontrolled input
class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = { value: 'hello' };
  }
  render() {
    return <input type='text' defaultValue={ this.state.value } />
  }
};

> The <input> element above is a little bit useless because the user updates the value but our component has no idea
> about that. We then have to use Refs to get access to the actual element.
class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = { value: 'hello' };
    this._change = this._handleInputChange.bind(this);
  }
  render() {
    return (<input type='text' defaultValue={ this.state.value } onChange={ this._change }
            ref={ input => this.input = input }/>)
  }
  _handleInputChange() {
    this.setState({ value: this.input.value });
  }
}

The ref prop receives a string or a callback.
The code above uses a callback and stores the DOM element into a local variable called input
Later when the onChange handler is fired we get the new value and send it to the App's state
Using a lot of refs is not a good idea. If it happens in your app consider using controlled input and re-think your components

所谓的受控输入和非受控输出 主要是判断该输入内容是否是响应式的 一般来说使用受控输入来完成我们所需要的需求

Final thoughts

controlled versus uncontrolled input is very often underrated(被低估的).
However I believe that it is a fundamental decision because it dictates the data flow in the React component.
uncontrolled inputs are kind of an anti-pattern and I’m trying to avoid them when possible.

Presentational and container components

There is a pattern which is used widely and helps organizing React based
applications - splitting the component into presentation and container.
将组件拆分为【表现层组件 - 渲染数据】和【容器层组件 - 控制数据】

// Bad example 
class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = { time: this.props.time };
    this._update = this._updateTime.bind(this);
  }
  render() {
    const time = this._formatTime(this.state.time);
    return (
      <h1>
        { time.hours } : { time.minutes } : { time.seconds }
      </h1>
    );
  }

  componentDidMount() {
    this._interval = setInterval(this._update, 1000);
  }

  componentWillUnmount() {
    clearInterval(this.interval);
  }
  
  _formatTime(time) {
    var [ hours, minutes, seconds ] = [
        time.getHours();
        time.getMiuntes();
        time.getSeconds();
    ].map(num => num < 10 ? '0' + num : num);
    
    return { hours, minutes, seconds };
  }

  _updateTime() {
    this.setState({
      time: new Date(this.state.time.getTime() + 1000);
    });
  }
}

ReactDOM.render(<Clock time = { new Date() } />, ....);

// Good example

// Clock/index.js
import Clock from './Clock.jsx'

export default class ClockContainer extends React.Component {
  constructor(props) {
    super(props);
    this.state = { time: props.time };
    this._update = this._updateTime.bind(this);
  }
  render() {
    return <Clock { ...this.extract(this.state.time) }/>;
  }
  componentDidMount() {
    this._interval = setInterval(this._update, 1000);
  }
  componentWillUnmount() {
    clearInterval(this._interval);
  }
  .extract(time) {
    return {
      hours: time.getHours();
      minutes: time.getMiuntes();
      seconds: time.getSeconds();
    };
  }
  _updateTime() {
    this.setState({
      time: new Date(this.state.time.getTime() + 1000);
    });
  }
}

Presentational component

Presentational component are concerned with how the things look.
They have the additional markup needed for making the page pretty.
Such components are not bound to anything and have no dependencies.
Very often implemented as a stateless functional component they don't have internal state
In our case the presentational component contains only the two-digits check and returns the

tag

// Clock/Clock.jsx
// Clock => 只用来渲染所接收的数据
export default function Clock(props) {
  var [ hours, minutes, seconds ] = [
    props.hours,
    props.minutes,
    props.seconds,
  ].map(num => num < 10 ? '0' + num : num);
  return <h1>{ hours } : { minutes } : { seconds } </h1>;
}

Benefits

Splitting the components in containers and presentation increases the reusability of the components.

One-way direction data flow (单向数据流)

It is around the idea that the components do not modify the data that they receive.
They only listen for changes of this data and maybe provide the new value but they do not update the actual data.
This update following another mechanism in another place and the component just gets re-rendered with the new value.

class Switcher extends React.Component {
  constructor(props) {
    super(props);
    this.state = { flag : false };
    this._onButtonClick = e => this.setState({
        flag: !this.state.flag;
    });
  }
  render() {
      return (
        <button onClick={ this._onButtonClick }>
            { this.state.flag ? 'lights on' : 'lights off' }
        </button>
      )
  }
}

function App() {
    return <Switcher/>;
}

https://www.bookstack.cn/read/react-in-patterns/book-chapter-09-README.md