2020-07-27
React
0

目录

ReactDOM.render
React.createElement
class 组件 constructor
props命名规则
组件命名规范
key
React.lazy
Context
class组件使用方式
函数式组件使用方式
displayName
Consumer
注意事项
ErrorBoundary
ref
React.forwardRef
总结
高阶组件
深入JSX
在 JSX 类型中使用点语法
JSX 会移除行首尾的空格以及空行。
布尔类型、Null 以及 Undefined 将会忽略
Portal
生命周期废弃
componentWillMount废弃原因:
componentWillUpdate 废弃原因:
componentWillReceiveProps 废弃原因:

当今最流行的JavaScript库之一React,因其强大的组件化开发能力而备受欢迎。React官方文档是学习和掌握React的重要资源之一,它提供了全面的指南、API文档和示例代码。因为现在的公司使用React进行开发项目,遂打算重新跟着文档学一遍React。

ReactDOM.render

jsx
ReactDOM.render( <Game />, // 提供html标签或者React组件标签,都可以被解析 document.getElementById('root') // 插入到的节点 );

返回的是一个对象,无标记值,但是提供了updater和isMounted

React.createElement

JSX语法会自动将html标签转换成React.createElement

JSX文件,每次需引入React插件,因为转换时,会用到React.createElement

jsx
// 转换前 const elObj=<div>result</div> // 转换后 const elObj = /*#__PURE__*/React.createElement("div", null, "result");

返回的是一个对象,介绍几个常用值:

  • key:唯一索引
  • props:当前组件props值
  • ref:当前组件ref值
  • type:当前 class 实例
  • _owner:虚拟节点值
js
$$typeofSymbol(react.element) keynull props: {a1"a1"a2"a2"a3: ƒ} ref"t1" typeclass Board _ownerFiberNode {tag1keynullstateNodeGameelementType: ƒ, type: ƒ, …} _store: {validatedfalse} _selfGame {props: {…}, context: {…}, refs: {…}, updater: {…}, _reactInternalFiberFiberNode, …} _source: {fileName"/Users/ccz/Public/student-project/cs/react-website-student/src/index.js"lineNumber50columnNumber23} __proto__Object

class 组件 constructor

  • 调用super的原因:在ES6中,在子类的constructor中必须先调用super才能引用this
  • super(props)的目的:在constructor中可以使用this.props

最后,可以看下React文档,里面有一段 Class components should always call the base constructor with props.

另外附上一个高分回答

image.png

props命名规则

通常会将代表事件的监听 prop 命名为on[Event],将处理事件的监听方法命名为 handle[Event]这样的格式

组件命名规范

组件名称必须以大写字母开头。

React 会将以小写字母开头的组件视为原生 DOM 标签。例如,

代表 HTML的 div 标签,而 <WeLcome />则代表一个组件,并且需在作用域内使用 WeLcome。

key

  • 组件无法访问props key
  • 默认将数组的索引当做index值
  • 仅当数组循环生成React元素时,会提示指定key值
  • 数组的key值并不需要全局唯一,保证同一级元素唯一即

image.png

React.lazy

  1. React.lazy 必须和 React.Suspense组件配合使用
jsx
import React, { Suspense } from 'react'; const OtherComponent = React.lazy(() => import('./OtherComponent')); function MyComponent() { return ( <div> <Suspense fallback={<div>Loading...</div>}> <OtherComponent /> </Suspense> </div> ); }
  1. React.lazy 目前只支持默认导出(default exports)

Context

class组件使用方式

  1. 使用React.createContext定义context
  2. 父组件使用Context.Provider包住
  3. 子组件Class 定义 contextType属性

context.js

js
// 建议使用单独js,否则容易出现ESM循环引用问题 import React from 'react' export const ThemeContext = React.createContext('light')

app.jsx

jsx
import { ThemeContext } from './context' import Child from './child' class App extends React.Component { render () { return ( <ThemeContext.Provider value="dark"> <Child/> </ThemeContext.Provider> ) } }

child.jsx

jsx
import React from 'react' import { ThemeContext } from './context' class Child extends React.Component { static contextType = ThemeContext; render () { console.log('context',this.context) return ( <div> </div> ) } } export default Board

函数式组件使用方式

  1. 使用React.createContext定义context(同上)
  2. 父组件使用Context.Provider包住(同上)
  3. 子组件使用Context.Consumer包住

child.jsx

jsx
import { ThemeContext } from './context' const Child = (props) => { return ( <ThemeContext.Consumer> { value =>{ console.log('value',value) return ( <div> </div> ) }} </ThemeContext.Consumer> ) }

displayName

React DevTools 使用该字符串来确定 context 要显示的内容。 示例,下述组件在 DevTools 中将显示为 MyDisplayName:

jsx
const MyContext = React.createContext(/* some value */); MyContext.displayName = 'MyDisplayName'; <MyContext.Provider> // "MyDisplayName.Provider" 在 DevTools 中 <MyContext.Consumer> // "MyDisplayName.Consumer" 在 DevTools 中

Consumer

多个Provider的情况下,可以创建多个对应的Consumer接收内容

注意事项

当value更新时,Provider包裹下的所有子组件都将被更新。 Provider value 直接赋值为对象时,建议抽离为state,直接使用对象赋值,会造成无用的元素更新,造成性能消耗。

ErrorBoundary

自 React 16 起,任何未被错误边界捕获的错误将会导致整个 React 组件树被卸载 image.png

通过最外层组件的 componentDidCatch生命周期进行捕获错误

jsx
import React from 'react' class ErrorBoundary extends React.Component { componentDidCatch(error, errorInfo) { // 你同样可以将错误日志上报给服务器 console.log(error, errorInfo); } render() { return this.props.children; } } export default ErrorBoundary
jsx
import React, { Suspense } from 'react'; import MyErrorBoundary from './MyErrorBoundary'; const OtherComponent = React.lazy(() => import('./OtherComponent')); const AnotherComponent = React.lazy(() => import('./AnotherComponent')); const MyComponent = () => ( <div> <MyErrorBoundary> <Suspense fallback={<div>Loading...</div>}> <section> <OtherComponent /> <AnotherComponent /> </section> </Suspense> </MyErrorBoundary> </div> );

ref

  • 当 ref 属性用于 HTML 元素时,构造函数中使用 React.createRef() 创建的 ref 接收底层 DOM 元素作为其 current 属性
  • 当 ref 属性用于自定义的 class 组件时, ref 对象接收组件的挂载实例作为其 current 属性,可以通过findDomNode获取dom
  • ref和key一样,不能当做props向下传递

注意:在React中ref属性仅仅用作获取dom值,在React16.3前,ref仅能拿到当前元素的dom情况。 当然我们可以通过定义其他function props去获取对应的dom

React.forwardRef

react内置高阶组件,可以透传ref,ref的值可以直接指定dom,也可以传对象

jsx
const FancyButton = React.forwardRef((props, ref) => ( <button ref={ref} className="FancyButton"> {props.children} </button> )); // 你可以直接获取 DOM button 的 ref const ref = React.createRef(); <FancyButton ref={ref}>Click me!</FancyButton>;

注意:在使用forwardRef时,应当将其视为一个破坏性更改,因为该组件会有明显不同的行为。 当我们使用高阶组件(HOC),我们在高阶组件使用ref,拿到的是高阶组件ref的实例,若我们想拿取Component的实例,需要定义额外的props字段。使用React.forwardRef可以直接使用ref 当做props进行透传。

总结

  • 若父组件想要获取子组件的方法,class组件可以在直接在子组件暴露当前实例
  • 函数式组件在hooks之前,值都是外层提供的,所以没有该需求,但是在hooks出来之后,函数式组件也可以维护自己的值和方法,可以使用React.forwardRef进行传递

高阶组件

1.不要改变原始组件。使用组合 不要试图在 HOC 中修改组件原型(或以其他方式改变它)。

js
function logProps(InputComponent) { InputComponent.prototype.componentDidUpdate = function(prevProps) { console.log('Current props: ', this.props); console.log('Previous props: ', prevProps); }; // 返回原始的 input 组件,暗示它已经被修改。 return InputComponent; } // 每次调用 logProps 时,增强组件都会有 log 输出。 const EnhancedComponent = logProps(InputComponent);

2.将不相关的 props 传递给被包裹的组件

HOC 为组件添加特性。自身不应该大幅改变约定。HOC 返回的组件与原组件应保持类似的接口。 HOC 应该透传与自身无关的 props。大多数 HOC 都应该包含一个类似于下面的 render 方法:

jsx
render() { // 过滤掉非此 HOC 额外的 props,且不要进行透传 const { extraProp, ...passThroughProps } = this.props; // 将 props 注入到被包装的组件中。 // 通常为 state 的值或者实例方法。 const injectedProp = someStateOrInstanceMethod; // 将 props 传递给被包装组件 return ( <WrappedComponent injectedProp={injectedProp} {...passThroughProps} /> ); }

3.包装显示名称以便轻松调试

最常见的方式是用 HOC 包住被包装组件的显示名称。比如高阶组件名为 withSubscription,并且被包装组件的显示名称为 CommentList,显示名称应该为 

jsx
WithSubscription(CommentList) function withSubscription(WrappedComponent) { class WithSubscription extends React.Component {/* ... */} WithSubscription.displayName = `WithSubscription(${getDisplayName(WrappedComponent)})`; return WithSubscription; } function getDisplayName(WrappedComponent) { return WrappedComponent.displayName || WrappedComponent.name || 'Component'; }

4.不要在 render 方法中使用 HOC

通常,你不需要考虑这点。但对 HOC 来说这一点很重要,因为这代表着你不应在组件的 render 方法中对一个组件应用 HOC:

js
render() { // 每次调用 render 函数都会创建一个新的 EnhancedComponent // EnhancedComponent1 !== EnhancedComponent2 const EnhancedComponent = enhance(MyComponent); // 这将导致子树每次渲染都会进行卸载,和重新挂载的操作! return <EnhancedComponent />; }

5.务必复制静态方法

深入JSX

在 JSX 类型中使用点语法

在 JSX 中,你也可以使用点语法来引用一个 React 组件。当你在一个模块中导出许多 React 组件时,这会非常方便。

jsx
import React from 'react'; const MyComponents = { DatePicker: function DatePicker(props) { return <div>Imagine a {props.color} datepicker here.</div>; } } function BlueDatePicker() { return <MyComponents.DatePicker color="blue" />;}

JSX 会移除行首尾的空格以及空行。

与标签相邻的空行均会被删除,文本字符串之间的新行会被压缩为一个空格。因此以下的几种方式都是等价的:

jsx
<div>Hello World</div> <div> Hello World </div> <div> Hello World </div> <div> Hello World </div>

布尔类型、Null 以及 Undefined 将会忽略

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

jsx
<div /> <div></div> <div>{false}</div> <div>{null}</div> <div>{undefined}</div> <div>{true}</div>

反之,如果你想渲染 false、true、null、undefined 等值,你需要先将它们转换为字符串:

jsx
<div>My JavaScript variable is {String(myVariable)}.</div>

Portal

Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案,Portal 组件即使不在父组件的dom节点内,父组件仍然能够捕获其事件

jsx
import React from 'react' import ReactDOM from 'react-dom' const Modal = (props) => { return ReactDOM.createPortal( <div className="modal-container">{props.children}</div>, document.querySelector('body') ); } export default Modal

生命周期废弃

componentWillMount废弃原因:

  1. fiber架构改动后,render前的生命周期都有可能被执行多次
  2. ajax请求放置在componentWillMount的作用意义并不大,事实上在 componentWillMount 执行后,第一次渲染就已经开始了,所以如果在 componentWillMount 执行时还没有获取到异步数据的话,页面首次渲染时也仍然会处于没有异步数据的状态。换句话说,组件在首次渲染时总是会处于没有异步数据的状态,所以不论在哪里发送数据请求,都无法直接解决这一问题
  3. 服务端渲染下,服务端和客户端会各执行一次该生命周期
  4. 可能会导致内存溢出,componentWillMount注册事件时,可能会导致事件多次被重复注册。

componentWillUpdate 废弃原因:

  1. fiber架构改动后,render前的生命周期都有可能被执行多次
  2. fiber架构改动后,生命周期会被打断,获取到的dom状态会不一样,可以使用getSnapshotBeforeUpdate代替

componentWillReceiveProps 废弃原因:

  1. fiber架构改动后,render前的生命周期都有可能被执行多次
  2. 没有一个明确的定位,往往用于实现通过 props 更新 state 的功能。但 componentWillReceiveProps 只在 Updating 中 props 更新时被调用,无法覆盖所有场景
  3. componentWillReceiveProps是更新阶段唯一一个可以主动setState的生命周期,导致开发者错误地在该函数中引入 side effect(副作用,调用外部函数等),使得组件重复多次执行 Updating。

本文作者:BARM

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!