React基础入门

1.JSX

React 使用 JSX 来替代常规的 JavaScript。

JSX 是一个看起来很像 XML 的 JavaScript 语法扩展。

Babel 会把 JSX 转译成一个名为 React.createElement() 函数调用。

条件渲染

内联方式: 与运算符&&,三目运算符

function Mailbox(props) {
  const unreadMessages = props.unreadMessages;
  return (
    <div>
      <h1>Hello!</h1>
      {unreadMessages.length > 0 &&        <h2>您有未读信息</h2> }
    </div>
  );
}

列表渲染

map方法,在 map() 方法中的元素需要设置 key 属性。key 值在兄弟节点之间必须唯一

JSX 允许在大括号中嵌入任何表达式,所以我们可以内联 map() 返回的结果。

const listItems = numbers.map((number) =>
    //key 应该在数组的上下文中被指定
    <ListItem key={number.toString()} value={number} />
  );

2.组件申明

2.1 组件申明

组件,从概念上类似于 JavaScript 函数。它接受任意的入参(即 “props”),并返回用于描述页面展示内容的 React 元素。

对于一些无状态的组件创建,建议使用函数式创建的方式,再比如hooks的机制下,函数式组件能做类组件对应的事情,所以建议都使用函数式的方式来创建组件

React 会将以小写字母开头的组件视为原生 DOM 标签。 组件名称必须以大写字母开头。

  • 通过函数申明组件:
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}
  • 通过class申明组件,继承React.Component
class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}
  • 通过 React.createClass 方法创建
React.createElement(
  "div", //type
  null, // props
  //children
  React.createElement("img", {
    src: "avatar.png",
    className: "profile"
  }),
  React.createElement(Hello, null)
);

React.createElement其被调用时会传⼊标签类型type,标签属性props及若干子元素children,作用是生成一个虚拟Dom对象.

3类组件申明方式的区别:

  • 函数式创建,在React Hooks出来之前,函数式组件可以视为无状态组件无生命周期无实例,只负责根据传入的props来展示视图,不涉及对state状态的操作。
  • 类继承创建,在react hooks出来之前,有状态的组件只能通过继承React.Component这种形式进行创建,有状态的组件也就是组件内部存在维护的数据,在类创建的方式中通过this.state进行访问,当调用this.setState修改组件的状态时,组价会再次会调用render()方法进行重新渲染
  • React.createClass,react刚开始推荐的创建组件,编写方式复杂。

在考虑组件的选择原则上,能用无状态组件则用无状态组件,现在推荐使用函数式创建+react hook实现有状态组件。

2.2 原理:Virtual DOM 和React diff

虚拟DOM会通过ReactDOM.render进行渲染成真实DOM

ReactDOM.render(element, container[, callback])

当首次调用时,容器节点里的所有 DOM 元素都会被替换,后续的调用则会使用 Reactdiff算法进行高效的更新

如果提供了可选的回调函数callback,该回调将在组件被渲染或更新之后被执行.

render大致实现方法如下:

function render(vnode, container) {
    console.log("vnode", vnode); // 虚拟DOM对象
    // vnode _> node
    const node = createNode(vnode, container);
    container.appendChild(node);
}

// 创建真实DOM节点
function createNode(vnode, parentNode) {
    let node = null;
    const {type, props} = vnode;
    if (type === TEXT) {
        node = document.createTextNode("");
    } else if (typeof type === "string") {
        node = document.createElement(type);
    } else if (typeof type === "function") {
        node = type.isReactComponent
            ? updateClassComponent(vnode, parentNode)
        : updateFunctionComponent(vnode, parentNode);
    } else {
        node = document.createDocumentFragment();
    }
    reconcileChildren(props.children, node);
    updateNode(node, props);
    return node;
}

我们了解到类组件通过调用setState方法, 就会导致render,父组件一旦发生render渲染,子组件一定也会执行render渲染,当我们想要更新一个子组件的时候,react的默认做法是调用所有组件的render,再对生成的虚拟DOM进行对比(黄色部分),如不变则不进行更新,黄色部分diff算法对比是明显的性能浪费的情况.

2.3 React性能优化

React 也存在 Diff算法,而元素key属性的作用是用于判断元素是新创建的还是被移动的元素,从而减少不必要的元素渲染

  • key 应该是唯一的

  • key不要使用随机值(随机数在下一次 render 时,会重新生成一个数字)

  • 使用 index 作为 key值,对性能没有优化

如何避免不必要的render来应付上面的问题,主要手段是通过shouldComponentUpdatePureComponentReact.memo

除此之外, 常见性能优化常见的手段有如下:

  • 避免在jsx中使用内联函数
  • 使用 React Fragments 避免额外标记
  • 使用 Immutable
  • 懒加载组件
  • 事件绑定方式
  • 服务端渲染

3.Props与States

所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。

props 理解为从外部传入组件内部的数据

react 具有单向数据流的特性,所以他的主要作用是从父组件向子组件中传递数据

props 除了可以传字符串,数字,还可以传递对象,数组甚至是回调函数

不要直接修改 State,这样代码不会重新渲染组件。构造函数是唯一可以给 this.state 赋值的地方。

应该使用 setState()
出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用。

因为 this.props 和 this.state 可能会异步更新,所以你不要依赖他们的值来更新下一个状态。
state 为局部的或是封装的的原因。除了拥有并设置了它的组件,其他组件都无法访问。
组件可以选择把它的 state 作为 props 向下传递到它的子组件中。

对同一个值进行多次 setStatesetState批量更新策略会对其进行覆盖,取最后一次的执行结果

//传入一个返回新state的函数
this.setState((prevState, props) => {
      return {count: prevState.count + 1};
    });
//传入新的state
this.setState({
        count: this.state.count + 1,
    })

props与states的区别:

相同点:

  • 两者都是 JavaScript 对象
  • 两者都是用于保存信息
  • props 和 state 都能触发渲染更新

区别:

  • props 是外部传递给组件的,而 state 是在组件内被组件自己管理的,一般在 constructor 中初始化
  • props 在组件内部是不可修改的,但 state 在组件内部可以进行修改
  • state 是多变的、可以修改

4.组件通信

React采用单向数据流

主要思想是组件不会改变接收的数据,只会监听数据的变化,当数据发生变化时它们会使用接收到的新值,而不是去修改已有的值。

多个组件需要反映相同的变化数据,建议将共享状态提升到最近的共同父组件中去(状态提升)。

  • 父组件向子组件传递,props
  • 子组件向父组件传递, 调用props传递的父组件提供的回调函数
  • 兄弟组件之间的通信,通过父组件通信
  • 父组件向后代组件传递,context
  • 非关系组件传递,建议做全局资源管理,例如使用redux

context的使用方法:

5.组件生命周期

img

constructor()

getDerivedStateFromProps()

render()

componentDidMount()

shouldComponentUpdate()

getSnapshotBeforeUpdate()

componentDidUpdate()

componentWillUnmount()

6.事件绑定与事件机制

React 事件的命名采用小驼峰式(camelCase),而不是纯小写。

必须谨慎对待 JSX 回调函数中的 this,在 JavaScript 中,class 的方法默认不会绑定 this。如果你忘记绑定 this.handleClick 并把它传入了 onClick,当你调用这个函数的时候 this 的值为 undefined。

this.handleClick = this.handleClick.bind(this);

如果你没有使用 class fields 语法,你可以在回调中使用箭头函数:此语法问题在于每次渲染 LoggingButton 时都会创建不同的回调函数。在大多数情况下,这没什么问题,但如果该回调函数作为 prop 传入子组件时,这些组件可能会进行额外的重新渲染。我们通常建议在构造器中绑定或使用 class fields 语法来避免这类性能问题。

key 帮助 React 识别哪些元素改变了,比如被添加或删除。因此你应当给数组中的每一个元素赋予一个确定的标识。

6.1 事件绑定this

  • render方法中使用bind(每次渲染都需要重新绑定,影响性能)
  • constructor中bind
  • render方法中使用箭头函数(每次渲染都需要生成新的方法,影响性能)
  • 定义阶段使用箭头函数绑定(推荐**)

6.2 react 事件与原生事件的区别

可以通过e.nativeEvent属性获取原生dom事件

  • 事件名称命名方式不同
// 原生事件绑定方式
<button onclick="handleClick()">按钮命名</button>
      
// React 合成事件绑定方式
const button = <button onClick={handleClick}>按钮命名</button>
  • 事件处理函数书写不同, React 事件的命名采用小驼峰式(camelCase),而不是纯小写。
// 原生事件 事件处理函数写法
<button onclick="handleClick()">按钮命名</button>
      
// React 合成事件 事件处理函数写法
const button = <button onClick={handleClick}>按钮命名</button>
  • React 所有事件都挂载在 document 对象上,而不是 React 组件对应的 DOM(减少内存开销就是因为所有的事件都绑定在 document 上,其他节点没有绑定事件)
  • 当真实 DOM 元素触发事件,先执行原生事件,然后处理React事件

React基于浏览器的事件机制自身实现了一套事件机制,包括事件注册、事件的合成、事件冒泡、事件派发等,在React中这套事件机制被称之为合成事件。合成事件是 React模拟原生 DOM事件所有能力的一个事件对象,即浏览器原生事件的跨浏览器包装器

6.3 ref绑定

Refs 在计算机中称为弹性文件系统。React 中的 Refs提供了一种方式,允许我们访问 DOM节点或在 render方法中创建的 React元素。

ref属性可用于原生HTML元素和类组件,设置为类组件时,ref对象接收到的是组件的挂载实例。

如何使用ref?

  • 传入字符串,使用时通过 this.refs.传入的字符串的格式获取对应的元素
  • 传入对象,对象是通过 React.createRef() 方式创建出来,使用时获取到创建的对象中存在 current 属性就是对应的元素
  • 传入函数,该函数会在 DOM 被挂载时进行回调,这个函数会传入一个 元素对象,可以自己保存,使用时,直接拿到之前保存的元素对象即可
  • 传入hook,hook是通过 useRef() 方式创建,使用时通过生成hook对象的 current 属性就是对应的元素

注意的是,不能在函数组件上使用ref属性,因为他们并没有实例。

在某些情况下,我们会通过使用refs来更新组件,但这种方式并不推荐,更多情况我们是通过propsstate的方式进行去重新渲染子元素。

应用场景:

  • 对Dom元素的焦点控制、内容选择、控制
  • 对Dom元素的内容设置及媒体播放
  • 对Dom元素的操作和对组件实例的操作
  • 集成第三方 DOM 库

7.React总结与特点

  • JSX语法
  • 单向数据绑定
  • 虚拟DOM
  • 声明式编程
  • Component

8.React hook

Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数。Hook 不能在 class 组件中使用 —— 这使得你不使用 class 也能使用 React。

数据获取、订阅或者手动修改过 DOM等操作被称作Effect。useEffect 就是一个 Effect Hook,给函数组件增加了操作副作用的能力。它跟 class 组件中的 componentDidMountcomponentDidUpdatecomponentWillUnmount 具有相同的用途。

hook概览:useState;useEffect;useContext;useReducer;useCallback;useMemo;useRef

默认情况下,React 会在每次渲染后调用effect函数 —— 包括第一次渲染的时候。。

import React, { useEffect, useState } from 'react';
//useState输入state初始值,返回当前state和更新state的函数
   function Example() {
   		const [count, setCount] = useState(0);

    return (
      <div>
        <p>You clicked {count} times</p>
            <button onClick={() => setCount(count + 1)}>
        Click me
        </button>
      </div>
    );
 }

effect函数还可以通过返回一个函数来指定如何“清除”副作用。

使用多个 Effect 实现关注点分离,使用 Hook 其中一个目的就是要解决 class 中生命周期函数经常包含不相关的逻辑,但又把相关逻辑分离到了几个不同方法中的问题。

useEffect(() => {    
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);    
    return () => {      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);    };  
});

通过跳过 Effect 进行性能优化

//`useEffect` 的第二个可选参数:
useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新

Hook使用规则:

  • 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
  • 只能在 React 的函数组件自定义Hook中调用 Hook。不要在其他 JavaScript 函数中调用。

调用了 useContext 的组件总会在 context 值变化时重新渲染。如果重渲染组件的开销较大,你可以 通过使用 memoization 来优化

useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。

在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为你可以向子组件传递 dispatch 而不是回调函数

useCallback返回一个 memoized 回调函数。它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。

9.其他概念

9.1 受控组件与非受控组件

受控组件的状态全程响应外部数据,一般需要初始状态和一个状态更新事件函数。

非受控组件,一般情况是在初始化的时候接受外部数据,然后自己在内部存储其自身状态。

大部分时候推荐使用受控组件来实现表单。

9.2 高阶组件

React中,高阶组件即接受一个或多个组件作为参数并且返回一个组件,本质也就是一个函数,并不是一个组件。

高阶组件的这种实现方式,本质上是一个装饰者设计模式

把通用的逻辑放在高阶组件中,对组件实现一致的处理,从而实现代码的复用。

高阶组件的主要功能是封装并分离组件的通用逻辑,让通用逻辑在组件间更好地被复用。

在实际应用中,常常用于与核心业务无关但又在多个模块使用的功能,如权限控制、日志记录、数据校验、异常处理、统计上报等。

9.3 Render Props

有时候我们会想要在组件之间重用一些状态逻辑。目前为止,有两种主流方案来解决这个问题:高阶组件render props自定义 Hook 可以让你在不增加组件的情况下达到同样的目的。

将一个组件封装的状态或行为共享给其他需要相同状态的组件并不总是显而易见。

使用 Render Props 来解决横切关注点

Render PropsReact.PureComponent 一起使用时要小心.

 使用 `render`prop 动态决定要渲染的内容,
          而不是给出一个 <Mouse> 渲染结果的静态表示
          
          <Mouse render={mouse => (
          <Cat mouse={mouse} />
          )}/>
posted @ 2022-05-07 17:09  鱼桑燕子梁  阅读(447)  评论(0编辑  收藏  举报