React: JSX 、虚拟 DOM、组件配置(props、state、PropTypes、createContext、props.children)

2022-07-28 08:36:52 浏览数 (1)

文章目录
  • 1. 虚拟 DOM
  • 2. JSX
  • 3. ReactComponent
  • 4. props 是参数
  • 5. PropTypes
  • 6. getDefaultProps() 获取默认 props
  • 7. 上下文
  • 8. 多个上下文
  • 9. state
    • 在setState中使用函数,而不是对象
  • 10. 无状态组件
  • 11. 使用 props.children 与子组件对话

learn from 《React全家桶:前端开发与实例详解》 https://zh-hans.reactjs.org/tutorial/tutorial.html https://zh-hans.reactjs.org/docs/create-a-new-react-app.html#create-react-app

1. 虚拟 DOM

我们操作虚拟DOM,让 React 负责更改浏览器的 DOM

虚拟 DOM,指的是,表示实际 DOM 的 JavaScript 对象树

开发人员只需要返回需要的 DOM,React 负责转换,且性能有优化,速度很快(高效的差异算法、更新子树、批量更新DOM)

ReactElement 是 虚拟 DOM 中对 DOM 元素的表示

先创建 RE,再 render (RE, 到实际的DOM挂载位置, 回调函数)

2. JSX

JSX 是 JavaScript Syntax Extension

JSX可以很方便的编写 ReactElement(无状态,不可变) 层次结构

babel 插件 可以 将 JSX 转译成 JS

JSX 通常用 () 包含起来,JSX属性 用 {} 包含, JSX 内部注释 {/* 注释 */}

JSX 使用 className 标识类

JSX 不能使用 for 属性,而是 htmlFor

3. ReactComponent

ReactComponent 是一个 JS 对象,至少有一个 render() 函数,返回一个 ReactElement

4. props 是参数

props 是组件的输入

props 可以传递任何 JS 对象

  • 基本类型、简单 JS 对象
  • 原子操作、函数、React元素、虚拟DOM节点

5. PropTypes

是验证 props 传递的值 的一种方法,属性名 : PropsTypes (string, number, boolean, function, object, array, arrayOf, node, element)

代码语言:javascript复制
import PropTypes from 'prop-types';
import React from 'react';

class DocumentedContainer extends React.Component {
  static propTypes = {
    children: PropTypes.oneOf([PropTypes.element, PropTypes.array])
  };

  render() {
    return <div className="container">{this.props.children}</div>;
  }
}

export default DocumentedContainer;

6. getDefaultProps() 获取默认 props

代码语言:javascript复制
class Counter extends React.Component {
  static defaultProps = {
  		initialValue: 1
  	}
  	...
}

使用 <Counter /> ,<Counter initialValue={1}/> 两种用法效果一致

7. 上下文

从 React 16.3.0 开始,可以指定通过组件树向下传递的变量,无需手动将变量从父组件传递到子组件

  • React.createContext 只接受一个参数,上下文提供的默认值

相当于 全局公开 的属性

例如网站主题: theme.js

代码语言:javascript复制
import React from 'react';

export const themes = {
  light: {
    foreground: '#222222',
    background: '#e9e9e9'
  },
  dark: {
    foreground: '#fff',
    background: '#222222'
  }
};

export const ThemeContext = React.createContext(themes.dark);

在 app 中: ThemeContext.Provider 用于把数据传递给子组件

代码语言:javascript复制
import React, {Component} from 'react';

import {ThemeContext, themes} from './theme';
import './App.css';
import Header from './Header';

class App extends Component {
  state = {theme: themes.dark};

  changeTheme = evt => {
    this.setState(state => ({
      theme: state.theme === themes.dark ? themes.light : themes.dark
    }));
  };

  render() {
    return (
      <div className="App">
        <ThemeContext.Provider value={this.state.theme}>
          <Header />
          <p className="App-intro">
            To get started, edit <code>src/App.js</code> and save to reload.
          </p>

          <button onClick={this.changeTheme}>Change theme</button>
        </ThemeContext.Provider>
      </div>
    );
  }
}

export default App;

Header.js ThemeContext.Consumer 从 Provider 父组件中获取主题

代码语言:javascript复制
import React from 'react';
import logo from './logo.svg';

import {ThemeContext} from './theme';

export const Header = props => (
  <ThemeContext.Consumer>
    {theme => (
      <header
        className="App-header"
        style={{backgroundColor: theme.background}}
      >
        <img src={logo} className="App-logo" alt="logo" />
        <h1 className="App-title" style={{color: theme.foreground}}>
          Welcome to React
        </h1>
      </header>
    )}
  </ThemeContext.Consumer>
);

export default Header;

8. 多个上下文

user.js

代码语言:javascript复制
import React from 'react';
export const UserContext = React.createContext(null);

Body.js

代码语言:javascript复制
import React from 'react';

import {ThemeContext} from './theme';
import {UserContext} from './user';

export const Body = props => (
    <ThemeContext.Consumer>
        {theme => (
            <header
                className="App-header"
                style={{backgroundColor: theme.background}}
            >
                <UserContext.Consumer>
                    <h1>{user => (user ? 'Welcome back' : 'Welcome')}</h1>
                </UserContext.Consumer>
            </header>
        )}
    </ThemeContext.Consumer>
);

export default Body;

9. state

Switch.css

代码语言:javascript复制
.active {
  background-color: #d5fffb;
  font-weight: bold;
}

支付选项:

代码语言:javascript复制
import React from "react";
import "../Switch.css";  // 导入样式

const CREDITCARD = "Creditcard";
const BTC = "Bitcoin";

class Switch extends React.Component {
    state = {
        payMethod: BTC // 默认支付选项
    };

    select = choice => {
        return evt => {
            this.setState({
                payMethod: choice
            }); // 返回一个函数
        };
    };

    renderChoice = choice => {
        // create a set of cssClasses to apply
        const cssClasses = ["choice"];  // 样式选项

        if (this.state.payMethod === choice) {
            cssClasses.push("active"); // add .active class
        } // 支付的选项样式,增加一个 active 样式

        return (
            <div className={cssClasses.join(" ")} onClick={this.select(choice)}>
                {choice}  
            </div>
        ); // 把样式渲染给 choice 
    };

    render() {
        return (
            <div className="switch">
                {this.renderChoice(CREDITCARD)}
                {this.renderChoice(BTC)}
                Pay with: {this.state.payMethod}
            </div>
        );
    }
}

export default Switch;

在setState中使用函数,而不是对象

为什么?下面是一个点击减少的按钮

使用对象的方式赋值给 state,如果用户点击过快,计算机非常慢,而 setState 是异步的,如果碰到更高优先级的响应过载,这个减少按钮的点击响应还在队列中等待,那么用户可能点了3次,但是最后数值只减少了1

  • 状态转换依赖于当前状态时,最好使用函数来设置状态,避免这种Bug
代码语言:javascript复制
  decrement = () => {
    // Appears correct, but there is a better way
    const nextValue = this.state.value - 1;
    this.setState({
      value: nextValue
    });
  };

更好的写法如下:

代码语言:javascript复制
  decrement = () => {
    this.setState(prevState => {
      return {
        value: prevState.value - 1
      };
    });
  };
  • 通常在组件里存的状态越少越好,最好是从外部获取,状态多了,会使得系统的状态是什么样子的变得难以推理
  • 可以使用多个无状态组件构成 一个有状态组件

10. 无状态组件

  • React 中 只需要 render() 方法的组件

无状态组件,它不是一个类,我们不会引用 this 这种函数式组件,性能更好

代码语言:javascript复制
const Header = function(props) {
  return (<h1>{props.headerText}</h1>)
}

有状态

代码语言:javascript复制
export class Header extends React.Component {
  render() {
    return (
      <h1>{this.props.headerText}</h1>
    );
  }
}

11. 使用 props.children 与子组件对话

可以使用 this.props.children 引用树中的子组件

代码语言:javascript复制
import PropTypes from 'prop-types';
import React from 'react';

class DocumentedContainer extends React.Component {
  static propTypes = {
    children: PropTypes.oneOf([PropTypes.element, PropTypes.array])
  };

  render() {
    return <div className="container">{this.props.children}</div>;
  }
}

export default DocumentedContainer;

处理子组件 :

  • map(),返回调用函数的结果的数组
  • forEach() 不收集结果
代码语言:javascript复制
import PropTypes from 'prop-types';
import React from 'react';

class MultiChildContainer extends React.Component {
  static propTypes = {
    component: PropTypes.element.isRequired,
    children: PropTypes.element.isRequired
  };

  renderChild = (childData, index) => {
    return React.createElement(
      this.props.component,
      {}, // <~ child props
      childData // <~ child's children
    );
  };

  render() {
    return (
      <div className="container">
        {React.Children.map(this.props.children, this.renderChild)}
      </div>
    );
  }
}

export default MultiChildContainer;
  • React.Children.toArray() 函数 转成子元素的数组
代码语言:javascript复制
import PropTypes from 'prop-types';
import React from 'react';

class ArrayContainer extends React.Component {
  static propTypes = {
    component: PropTypes.element.isRequired,
    children: PropTypes.element.isRequired
  };

  render() {
    const arr = React.Children.toArray(this.props.children);

    return <div className="container">{arr.sort((a, b) => a.id < b.id)}</div>;
  }
}

export default ArrayContainer;

0 人点赞