React 基础及核心总结/一

1. 元素属性及props

1)组件可以接受任意 props,包括基本数据类型,React 元素以及函数。

2)组件接到父组件传来的props是只读的,绝不能够被修改的。这也叫做单向数据流。

3)组件中的标签元素与原生的dom元素不同,其会被转义为对象(虚拟dom)。组件元素的属性与事件与原生的也不尽相同。比如class 在 JSX 中被写作 classNameonclick 在 JSX 中被写作 onClickfor 在 JSX 中被写作 htmlFor。这些新构建的属性和事件一般都是用小驼峰的书写形式。

4)不要将 props “镜像”给 state,请考虑直接使用 props。否则可能会产生BUG(当一个派生 state 值被 setState 方法更新时,你的prop是否需要更新?)。

5)属性可使用展开表达式将所有props一次性赋值:

function App1() {
  return <Greeting firstName="Ben" lastName="Hector" />;
}
// 等价于
function App2() {
  const props = {firstName: 'Ben', lastName: 'Hector'};
  return <Greeting {...props} />;
}

6)属性可以条件赋值

const Button = props => {
  const { kind, ...other } = props;
  const className = kind === "primary" ? "PrimaryButton" : "SecondaryButton";
  return <button className={className} {...other} />;
};

const App = () => {
  return (
    <div>
      <Button kind="primary" onClick={() => console.log("clicked!")}>
        Hello World!
      </Button>
    </div>
  );
};

2. state 和 setState

1)不要直接修改 State,直接修改将不会重新渲染组件,而是应该使用 setState(),类似于小程序中的setData()。如this.state.comment = 'Hello';是错误的。这里也能看出,React的数据是单向绑定且单向流动。

2)Class组件中,构造函数(constructor)是唯一能够直接设置State的地方。但现在一般都使用函数式组件及hook方法取代Class组件了。

3)Class组件中,setState方法会浅合并对象(Object.assign)。而在hook方法中,useState 导出的set方法不会自动合并更新对象。你可以用函数式的 setState 结合展开运算符来达到合并更新对象的效果。

const [state, setState] = useState({});
setState(prevState => {
  // 也可以使用 Object.assign
  return {...prevState, ...updatedValues};
});

3. Refs 和 Dom

1)Refs 创建

在React中也有refs,和Vue等其他框架一样,同样在标签中使用ref属性。Refs 允许我们访问 DOM 节点或在 render 方法中创建的 React 元素。但非必要尽量避免能够使用props解决的问题而使用refs

在Class组件中使用React.createRef()来创建ref,或在函数式组件内部使用名为useRef()的hook来创建ref。一般情况下可以使用ref的current属性来访问该元素的dom节点或该自定义组件的实例。不可在函数组件父级中对函数组件使用ref,因为函数组件没有实例。

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

  function handleClick() {
    textInput.current.focus();
  }
  
  // 这样做是正确的(在函数组件内部访问元素dom)
  return (
    <div>
      <input
        type="text"
        ref={textInput} />
      <input
        type="button"
        value="Focus the text input"
        onClick={handleClick}
      />
    </div>
  );
}

class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.textInput = React.createRef();
  }
  render() {
    // 这样做是错误的,无法访问的(在函数组件外部访问组件实例)
    return (
      <MyFunctionComponent ref={this.textInput} />
    );
  }
}

useRef() 比 ref 属性更有用。它可以很方便地保存任何可变值,其类似于在 class 中使用实例字段的方式。

这是因为它创建的是一个普通 Javascript 对象。而 useRef() 和自建一个 {current: ...} 对象的唯一区别是,useRef 会在每次渲染时返回同一个 ref 对象。

请记住,当 ref 对象内容发生变化时,useRef 并不会通知你。变更 .current 属性不会引发组件重新渲染。如果想要在 React 绑定或解绑 DOM 节点的 ref 时运行某些代码,则需要使用回调 ref 来实现。

2)回调 Refs

你可以在组件间传递回调形式的 Refs,也就是使用回调函数的形式创建Refs,这个函数中接受 React 组件实例或 HTML DOM 元素作为参数,以使它们能在其他地方被存储和访问。比如将回调函数通过props传递给子组件,这样父子组件都可以访问到子组件中的元素。

React 将在组件挂载时,会调用 ref 回调函数并传入 DOM 元素,当卸载时调用它并传入 null。在 componentDidMount 或 componentDidUpdate 触发前,React 会保证 refs 一定是最新的。

回调Refs创建和使用

class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);

    this.textInput = null;

    this.setTextInputRef = element => {
      this.textInput = element;
    };

    this.focusTextInput = () => {
      // 使用原生 DOM API 使 text 输入框获得焦点
      if (this.textInput) this.textInput.focus();
    };
  }

  componentDidMount() {
    // 组件挂载后,让文本框自动获得焦点
    this.focusTextInput();
  }

  render() {
    // 使用 `ref` 的回调函数将 text 输入框 DOM 节点的引用存储到 React
    // 实例上(比如 this.textInput)
    return (
      <div>
        <input
          type="text"
          ref={this.setTextInputRef}
        />
        <input
          type="button"
          value="Focus the text input"
          onClick={this.focusTextInput}
        />
      </div>
    );
  }
}

父子组件共享Refs

function CustomTextInput(props) {
  return (
    <div>
      <input ref={props.inputRef} />
    </div>
  );
}

class Parent extends React.Component {
  render() {
    return (
      <CustomTextInput
        inputRef={el => this.inputElement = el}
      />
    );
  }
}

在函数式组件中可以使用useCallback()来创建具有变化监听的回调Ref

function MeasureExample() {
  const [height, setHeight] = useState(0);

  const measuredRef = useCallback(node => {
    if (node !== null) {
      setHeight(node.getBoundingClientRect().height);
    }
  }, []);

  return (
    <>
      <h1 ref={measuredRef}>Hello, world</h1>
      <h2>The above header is {Math.round(height)}px tall</h2>
    </>
  );
}
// 没有选择使用 useRef,因为当 ref 是一个对象时它并不会把当前 ref 的值的变化通知到我们。
// 注意到我们传递了 [] 作为 useCallback 的依赖列表。这确保了 ref callback 不会在再次渲染时改变,因此 React 不会在非必要的时候调用它。
// 在此示例中,当且仅当组件挂载和卸载时,callback ref 才会被调用,因为渲染的 <h1> 组件在整个重新渲染期间始终存在。
// 如果你希望在每次组件调整大小时都收到通知,则可能需要使用 ResizeObserver 或基于其构建的第三方 Hook。

4. 元素中表达式渲染的一些问题

falsenullundefined, and true 是合法的子元素。但它们并不会被渲染。以下的 JSX 表达式渲染结果相同:

<div />

<div></div>

<div>{false}</div>

<div>{null}</div>

<div>{undefined}</div>

<div>{true}</div>

值得注意的是有一些 “falsy” 值(被类型转义),如数字 0,仍然会被 React 渲染。这个特点和其他的一些框架会有些出入。

例如,以下代码并不会像你预期那样工作,因为当 props.messages 是空数组时,将会渲染为数字 0

// 如下会被渲染为数字0
<div>
  {props.messages.length &&
    <MessageList messages={props.messages} />
  }
</div>

// 需要渲染右侧组件,&& 之前的表达式应总是布尔值:
<div>
  {props.messages.length > 0 &&
    <MessageList messages={props.messages} />
  }
</div>
加载中...
加载中...