React 基础及核心总结/三

1. 原生属性的改写和差异

在 React 中,所有的 DOM 特性和属性(包括事件处理)都应该是小驼峰命名的方式。例如,与 HTML 中的 tabindex 属性对应的 React 的属性是 tabIndex,再比如readOnly和。例外的情况是 aria-* 和 data-* 属性以及自定义的属性,一律使用小写字母命名。比如, 可以用 aria-label 作为 aria-label

1)checked 和 defaultChecked

当 <input> 组件的 type 类型为 checkbox 或 radio 时,组件支持 checked 属性,这个属性在原生的DOM中也是有的。你可以使用它来设置组件是否被选中。这对于构建受控组件(controlled components)很有帮助。而 defaultChecked 则是非受控组件的的新增属性,用于设置组件首次挂载时是否被选中。

2)className

className 属性用于指定 CSS 的 class,此特性适用于所有常规 DOM 节点和 SVG 元素,如 <div>,<a> 及其它标签。

如果你在 React 中使用 Web Components(这是一种不常见的使用方式),请使用 class 属性代替。

3)dangerouslySetInnerHTML

innerHTML 的替换方案,类似于Vue中的v-html指令。通常来讲,使用代码直接设置 HTML 存在风险,因为很容易无意中使用户暴露于跨站脚本(XSS)的攻击。因此,你可以直接在 React 中设置 HTML,但当你想设置 dangerouslySetInnerHTML 时,需要向其传递包含 key 为 __html 的对象。

const root = ReactDOM.createRoot(document.getElementById('root'));

function createMarkup() {
  return {__html: '<h1 style="color:red" class="h">我是标题</h1>'};
}

function MyComponent() {
  return <div dangerouslySetInnerHTML={createMarkup()} />;
}

root.render(<MyComponent/>);

4)htmlFor

由于 for 在 JavaScript 中是保留字,所以 React 元素中使用了 htmlFor 来代替。

<label htmlFor="namedInput">Name:</label>
<input id="namedInput" type="text" name="name"/>

5)onChange

onChange 事件与预期行为一致:每当表单字段变化时,该事件都会被触发。我们故意没有使用浏览器已有的默认行为,是因为 onChange 在浏览器中的行为和名称不对应,并且 React 依靠了该事件实时处理用户输入。

6)selected

如果要将 <option> 标记为已选中状态,请在 select 标签的 value 中引用该选项的值,而非使用selected,这与原生Html完全不同。总的来说,这使得 <input type="text">, <textarea><select> 之类的标签都非常相似—它们都接受一个 value 属性,你可以使用它来实现受控组件。

你可以将数组传递到 value 属性中,以支持在 select 标签中选择多个选项:

<select multiple={true} value={['B', 'C']}>

7)style

不推荐将 style 属性作为直接设置元素样式的方式。在多数情况下,应使用 className 搭配外部 CSS 样式表中定义的 class。style 在 React中多用于添加动态计算的样式。

添加动态计算样式时,style 接受一个采用小驼峰命名属性的 JavaScript 对象(部分浏览器引擎前缀为大驼峰),而不是 CSS 字符串。这与 DOM 中 style 的 JavaScript 属性是一致的。

const divStyle = {
  color: 'blue',
  backgroundImage: 'url(' + imgUrl + ')',
  WebkitTransition: 'all', // note the capital 'W' here
  msTransition: 'all' // 'ms' is the only lowercase vendor prefix
};

function HelloWorldComponent() {
  return <div style={divStyle}>Hello World!</div>;
}

React 会自动添加 ”px” 后缀到内联样式为数字的属性后。如需使用 ”px” 以外的单位,请将此值设为数字与所需单位组成的字符串。

// Result style: '10px'
<div style={{ height: 10 }}>
  Hello World!
</div>

// Result style: '10%'
<div style={{ height: '10%' }}>
  Hello World!
</div>

8)value 和 defaultValue

<input><select> 和 <textarea> 组件支持 value 属性。你可以使用它为组件设置 value。这对于构建受控组件是非常有帮助。defaultValue 属性对应的是非受控组件的属性,用于设置组件第一次挂载时的 value。

2. 生命周期(常用)

1)constructor()

在 React 组件挂载之前,会调用它的构造函数。在为 React.Component 子类实现构造函数时,应在其他语句之前调用 super(props)。否则,this.props 在构造函数中可能会出现未定义的 bug。

通常,在 React 中,构造函数仅用于以下两种情况:

在 constructor() 函数中不要调用 setState() 方法。如果你的组件需要使用内部 state,请直接在构造函数中为 this.state 赋值初始 state

ps:函数式组件不存在构造函数。函数本身就是构造函数。

2)componentDidMount()

在组件挂载后(插入 DOM 树中)立即调用。依赖于 DOM 节点的初始化应该放在这里。如需通过网络请求获取数据,此处是实例化请求的好地方。

3)componentDidUpdate()

在更新后会被立即调用。首次渲染不会执行此方法。

当组件更新后,可以在此处对 DOM 进行操作。如果你对更新前后的 props 进行了比较,也可以选择在此处进行网络请求。(例如,当 props 未发生变化时,则不会执行网络请求)。

在这里调用 setState()时必须被包裹在一个条件语句里,否则会导致死循环。它还会导致额外的重新渲染,虽然用户不可见,但会影响组件性能。

4)componentWillUnmount()

会在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等。

componentWillUnmount() 中不应调用 setState(),因为该组件在此之后将不会重新渲染。

5)static getDerivedStateFromError(error)

此生命周期会在后代组件抛出错误后被调用。 它将抛出的错误作为参数,并返回一个值以更新 state。

一般用于构建一个用于外层嵌套的自定义组件,以便在子组件出现错误时降级显示此UI。

ps:getDerivedStateFromError() 会在渲染阶段调用,因此不允许出现副作用(这里只能是纯函数,不允许修改其余变量)。 如遇此类情况,请改用 componentDidCatch()

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // 更新 state 使下一次渲染可以显降级 UI
    return { hasError: true };
  }

  render() {
    if (this.state.hasError) {
      // 你可以渲染任何自定义的降级  UI
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children;
  }
}

6)componentDidCatch(error, info)

此生命周期在后代组件抛出错误后被调用。 它接收两个参数:

  1. error —— 抛出的错误。
  2. info —— 带有 componentStack key 的对象,其中包含有关组件引发错误的栈信息。

ps:componentDidCatch() 会在“提交”阶段被调用,因此允许执行副作用。 它应该用于记录错误之类的情况:

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // 更新 state 使下一次渲染可以显示降级 UI
    return { hasError: true };
  }

  componentDidCatch(error, info) {
    // "组件堆栈" 例子:
    //   in ComponentThatThrows (created by App)
    //   in ErrorBoundary (created by App)
    //   in div (created by App)
    //   in App
    logComponentStackToMyService(info.componentStack);
  }

  render() {
    if (this.state.hasError) {
      // 你可以渲染任何自定义的降级 UI
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children;
  }
}

7)函数式组件中,useEffect() 可以取代一部分生命周期

可以把 useEffect Hook 看做 componentDidMountcomponentDidUpdate 和 componentWillUnmount 这三个函数的组合。具体如下:

每次我们重新渲染,都会生成新的 effect,替换掉之前的。某种意义上讲,effect 更像是渲染结果的一部分 —— 每个 effect “属于”一次特定的渲染。

与 componentDidMount 或 componentDidUpdate 不同,React 会等待浏览器完成画面渲染之后才会延迟调用 useEffect,因此会使得额外操作很方便。使用 useEffect 调度的 effect 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快。大多数情况下,effect 不需要同步地执行。在个别情况下(例如测量布局),有单独的 useLayoutEffect Hook 供你使用,其 API 与 useEffect 相同。

你也可以使用多个 effect。这会将不相关逻辑分离到不同的 effect 中。但你不可将effect hook放到条件判断中(所有的hooks都不可以)。

effect 只要传递数组作为 useEffect 的第二个可选参数(deps依赖数组)即可限制每次渲染都执行,只会在传入的依赖变化时才执行。需要重点注意的是:请确保数组中包含了所有外部作用域中会随时间变化并且在 effect 中使用的变量,否则你的代码会引用到先前渲染中的旧变量。

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新

在每次渲染后都执行,类似于componentDidUpdate

useEffect(() => {
    document.title = `You clicked ${count} times`;
});

如果 effect 返回一个函数,React 将在执行组件卸载操作时调用它,类似于componentWillUnmount

useEffect(() => { 
    document.title = `You clicked ${count} times`;
    return ()=>{
        document.title = '';
    }
});

如果你的 effect 第二个参数传入一个空数组,则只会在组件挂载后(插入 DOM 树中)调用。类似于componentDidMount

useEffect(() => { 
    document.title = `You clicked ${count} times`; 
},[]);

3. 数据监听

useEffect 实现数据监听的效果,类似于其他框架中的watch

const [count,setCount] = useState(0)
useEffect(() => { 
    console.log('我是改变后的count',count)
}, [count]); 
加载中...
加载中...