React 复习
React的三个核心依赖库
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
使用React
需要引入上述的三个库,crossorigin
这个属性是可以获取到跨站脚本的错误信息。
关于React
为何要分为三个库主要是因为react-native
因为手机端的react-dom
会将React
渲染出来的虚拟对应到原生的控件上,在web
端就是生成真实的dom
。
为什么要引入babel
babel在前端,用途是非常的广泛。作为前端经常使用的代码转换器和编译器。可以对js
做兼容性处理,比如es6
转es5
。
默认React
的开发可以不需要babel
,但是我们要写一些html
模板就需要使用React.createElement
这个函数,但是要先一些嵌套的额标签的话,那是十分繁琐的。所以我们可以编写jsx
代码(JavaScript XML),babel
的作用就是将jsx
语法转换为React.createElement
写法。
比如:
左边我们可以按照html
的写法创建标签,而经过babel编译后最终为React.createElement
形式,可以看到该形式是较为复杂。
JSX语法
我们要编写jsx
语法需要将代码写在如下标签中,表示我们写的为jsx
代码。
<script type="text/babel">
let message = 'hello saber';
function btnClick() {
message = 'hello react';
render();
}
function render() {
const VDOM = (
<div>
<div>{message}</div>
<button onClick={btnClick}>改变文本</button>
<div>
<span>你</span>
<span>好</span>
</div>
</div>
)
ReactDOM.render(VDOM, document.getElementById('test'))
}
render();
</script>
jsx的书写规范
- 首先
jsx
顶层只能有一个根元素,我们需要在外层包裹一个div
,或者Fragment
标签。 - 如果我们要换行书写还需要在外层包裹一个
()
。 jsx
支持单标签但是要以/>
结尾。jsx
内部要混入变量使用{}
,其中也可以使用一些表达式。- 标签的属性如果与一些保留词冲突的话需要改写,比如样式的类名
class
需要使用className
,另外还有label
标签中的for
属性,要使用htmlFor
来代替。 - 内联样式,要用
style={{key:value}}
的形式去写。
{}中能展示的变量类型
可以展示的类型:string, number,Array
(会将每一项拼在一起成字符串)。
不能展示的类型:undefined,null,boolean
,一般对象不能不能作为jsx
的子类即对象不能直接放置在{}中。
在{}
不能展示的变量类型,如果需要展示的话可以转换变量类型或者调用toString()
等方法。
条件渲染
当逻辑判断很多的时候可以使用if else
来判断,简单的情况可以使用三目运算符或者交集运算符。
还可以根据性能要求使用display:none
或者操作dom
的方式来切换dom
显示。
<script type="text/babel">
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
isLogin: true
}
}
render() {
const { isLogin } = this.state;
// 1.方案一:通过if判断: 逻辑代码非常多的情况
let welcome = null;
let btnText = null;
if (isLogin) {
welcome = <h2>欢迎回来~</h2>
btnText = "退出";
} else {
welcome = <h2>请先登录~</h2>
btnText = "登录";
}
return (
<div>
<div>我是div元素</div>
{welcome}
{/* 2.方案二: 三元运算符 */}
<button onClick={e => this.loginClick()}>{isLogin ? "退出" : "登录"}</button>
<hr />
<h2>{isLogin ? "你好啊, coderwhy": null}</h2>
{/* 3.方案三: 逻辑与&& */}
{/* 逻辑与: 一个条件不成立, 后面的条件都不会进行判断了 */}
<h2>{ isLogin && "你好啊, coderwhy" }</h2>
{ isLogin && <h2>你好啊, coderwhy</h2> }
</div>
)
}
loginClick() {
this.setState({
isLogin: !this.state.isLogin
})
}
}
ReactDOM.render(<App />, document.getElementById("app"));
</script>
事件绑定和this
jsx
中的dom
事件绑定就是在指定的dom
上以onXxxx
形式绑定事件
<h2 onClick={this.btnClick}>点击</h2>
关于绑定事件函数中的this
指向问题。上述函数中的this
不做任何处理时默认指向undefined
的。
因为React
在处理绑定的事件函数内部通过 btnClick.apply(undefined,[arg])
来调用的,内部是无法获得该上下文的this
的。
解决方式1:显示绑定this
this.btnClick.bind(this);
解决方式2:使用箭头函数来定义事件函数
btnClick = () => {
console.log(this)
}
解决方式3:在{}是用箭头函数返回事件函数
<button onClick={() => { this.decrement("why") }}>-1</button>
推荐使用第三种,因为传值的时候也不需要使用柯里化来定义事件函数了。
jsx本质
jsx
实际上就是React.createElement
的语法糖
通过createElement
创建出来的对象就是ReactElement
对象,最终形成一个虚拟DOM
react
渲染流程:
jsx -> createElement函数 -> ReactElement(对象树) -> ReactDOM.render -> 真实的DOM
react native
渲染流程:
jsx -> createElement函数 -> ReactElement(对象树) -> ReactDOM.render -> 原生的控件(UIButton/Button)
源码展示:
export function createElement(type, config, children) {
let propName;
// Reserved names are extracted
const props = {};
let key = null;
let ref = null;
let self = null;
let source = null;
if (config != null) {
if (hasValidRef(config)) {
ref = config.ref;
if (__DEV__) {
warnIfStringRefCannotBeAutoConverted(config);
}
}
if (hasValidKey(config)) {
key = '' + config.key;
}
self = config.__self === undefined ? null : config.__self;
source = config.__source === undefined ? null : config.__source;
// Remaining properties are added to a new props object
for (propName in config) {
if (
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)
) {
props[propName] = config[propName];
}
}
}
// Children can be more than one argument, and those are transferred onto
// the newly allocated props object.
//多余的参数都会放到一个数组中来处理
const childrenLength = arguments.length - 2;
if (childrenLength === 1) {
props.children = children;
} else if (childrenLength > 1) {
//开辟一个数组,使得插入更高效
const childArray = Array(childrenLength);
//遍历添加
for (let i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
if (__DEV__) {
if (Object.freeze) {
Object.freeze(childArray);
}
}
props.children = childArray;
}
// Resolve default props
if (type && type.defaultProps) {
const defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
if (__DEV__) {
if (key || ref) {
const displayName =
typeof type === 'function'
? type.displayName || type.name || 'Unknown'
: type;
if (key) {
defineKeyPropWarningGetter(props, displayName);
}
if (ref) {
defineRefPropWarningGetter(props, displayName);
}
}
}
return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props,
);
}
React的组件化
创建组件方式
React
有两种创建组件的方式:类式组件和函数式组件。
另外根据组件的其他概念,比如状态,职责,UI等,可分为:有状态组件,无状态组件,展示型组件和容器型组件,异步组件,高阶组件等。
类式组件
- 首先无论是类组件还是函数式组件,创建时的名称首字母必须大写,不然会被识别为
html
的内置标签。 - 类组件实现需要继承
React.component
或者React.pureComponent
这个父类组件。 - 类组件必须实现
render
函数,该函数也是唯一必须实现的。
官网现在推荐使用ES6的类定义语法来定义类式组件。
import React, { Component } from 'react'
export default class componentName extends Component {
render() {
return (
<div>
</div>
)
}
}
函数式组件
函数式组件就是使用function
函数表达式来定义组件,需要返回的内容结构和类组件中render
函数返回的结构相同。
相比类式组件,函数式组件有以下特点:
- 没有生命周期,类式组件有一套自己的生命周期函数。
- 没有
this
实例,类式组件的this
指向实例。 - 没有内部的状态(
state
),类式组件可以通过在this.state
中定义状态变量。
export default function ComponentName(){
return (
<div></div>
)
}
生命周期函数
React
组件的生命周期函数表示了组件从开始创建到渲染成功,到最后销毁这一系列的过程。我们可以通过这些生命周期函数来在不同的组件阶段实现各种功能。
函数式组件可以通过hooks
来模拟生命周期函数,这里我们主要谈类式组件的生命周期函数。
constructor
谈之前我们先谈下类式组件的构造函数constructor
。
我们使用constructor
一般用来初始化组件的state
,或者给事件绑定this
,或者父类的构造函数来保存props
。
constructor(props){
super(props);
this.state = {message:'hello world'}
}
上述的构造函数可以省去不写,因为默认不写时,React在执行生成实例时就会调用上述实现方式的构造函数。
如果不写构造函数,我们也可以将state
直接写在类式作用域中,调用方式不变:
class MyComponent extends React.Component {
//初始化状态
state = {isHight:false}
render() {
const {isHight} = this.state;
return <h2>你{isHight ? '身体一定很好' : '是个LSP'}</h2>
}
}
但是还有一个属性props
,如果我们没有在构造函数中声明保存,这个属性我们也可以直接在render
获取。
export default class index extends Component {
// constructor(props) {
// super(props);
// }
render() {
const { name, age } = this.props;
return (
<div>
我是home组件
<span>
<br />
{name}---{age}
</span>
</div>
);
}
}
props
是获取从父组件传递来的参数的属性,之所以render
函数能够获取到props
,是内部源码做了一层处理,将props
保存了下来。所以我们可以直接使用props
。
生命周期函数在React
的不同版本有所不同,本篇所讲的主要针对于React
版本大于16.13.1
。
旧版生命周期函数图解:
新版生命周期函数图解:
旧版的生命周期函数已经不推荐使用了,当然仍然可以使用。如果要使用的话需要在函数名前加上UNSAFE_
前缀。我们不加前缀的话React
会内部转发调用带前缀的生命周期函数,同时会报警告。
以下生命周期是过时的的:
UNSAFE_componentWillMount()
组件即将挂载成功。UNSAFE_componentWillReceiveProps()
在已挂载的组件接收新的props
之前被调用。UNSAFE_componentWillUpdate()
当组件收到新的props
或state
时。会在渲染之前调用该函数。
新版生命周期相对要复杂些:
componentDidMount
当我们的组件第一次挂载完毕(插入DOM树)后,就会执行该函数。我们可以在这个生命周期函数中执行一些初始化的事,比如:一些需要依赖DOM的操作(获取一些DOM元素属性),网络请求,一些事件的订阅(对应在componentWillUnmount
中取消订阅)。
componentDidUpdate
当组件更新完后会被立即执行,首次渲染不会执行该方法。一般我们是通过this.setState
或者render
函数调用来触发这个函数。该函数可以获取上一次的state
,props
和snapshot
(快照值)。可以比较是否改变来执行相应的操作。注意我们可以在该函数中调用this.setState
方法,但是要注意需要条件判断执行,否则会出现死循环。
componentWillUnmount
组件即将卸载的函数,可以在该函数中执行取消订阅,网络请求取消或者引用销毁等操作。我们可以手动调用ReactDOM.unmountComponentAtNode()
来卸载指定的节点
getDerivedStateFromProps
这个函数并不常用,他是定义在类上的静态方法需要加上类名或者static
。当state
的值在任何时候都是取决于props
就可以使用该函数。该方法返回一个对象来更新state
getSnapshotBeforeUpdate
在组件即将更改前执行,可以获取一些DOM信息,然后返回。我们可以通过componentDidUpdate
这个函数来接受返回值。
shouldComponentUpdate
React组件会根据这个函数返回的值来确定是否更新,默认的行为是state每次发生变化组件都会更新。
当 props
或 state
发生变化时,shouldComponentUpdate()
会在渲染执行之前被调用。返回值默认为 true
。这里注意如果调用forceUpdate
强制更新,会跳过该函数执行。关于该函数的其他知悉可以了解:[React性能优化]()
总结:
初始化阶段,由ReactDOM.render()触发---初次渲染
constructor()
static getDerivedStateFromProps()
render()
componentDidMount()
更新阶段,由组件内部
this.setSate()
或父组件重新render
触发static getDerivedStateFromProps()
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate
componentDidUpdate()
卸载阶段:由内部
ReactDOM.unmountComponentAtNode()
触发componentWillUnmount
组件的通信方式
在组件中我们会经常遇到组件相互传值的方式,其中需要用到props
这个重要的属性。
父组件传递给子组件
父组件通过在子组件标签上以 属性=值 方式传递数据给子组件。
父组件:
export default class SpuerCon extends Component {
render(){
return (
<ChildrenCon name='zhangsan' age={18} />
)
}
}
类子组件接收:
export default class ChildrenCon extends Component {
render(){
return (
<h2>
{this.props.name}---{this.props.age}
</h2>
)
}
}
函数组件接收:
export function ChildrenCon(props){
return (
<h2>{props.name}---{props.age}</h2>
)
}
如果使用过Vue
,我们给子组件传递值的时候,子组件可以对传递过来的props
进行验证。React
也可以对传递过来的props
进行验证,但是React
自身并不能验证,我们需要借助第三方库或者TS来进行验证。这里第三方库推荐prop-types
。
//npm安装方式
npm install prop-types -S
//yarn 安装方式
yarn add prop-types
使用方式:
import React, { Component } from "react";
import PropTypes from 'prop-types';
export default class ChildrenCon extends Component {
render(){
const {name, age} = this.props;
return (
<h2>{name}---{age}</h2>
)
}
//类型验证
static propTypes = {
name:PropTypes.string.isRequired
}
//默认值设置
static defaultProps = {
age:100
}
}
子组件传递给父组件
在Vue
中子传递父是通过自定义事件实现,在React
中就还是通过Props
实现的,我们可以向子组件传递一个函数,让子组件执行来实现子传父。
import React, { Component } from "react";
//子组件
class Btn extends Component {
render() {
return <button onClick={this.props.increment}>+</button>;
}
}
//父组件
export default class CpnBtn extends Component {
constructor(props) {
super(props);
this.state = {
counter: 0,
};
}
render() {
return (
<div>
<div>{this.state.counter}</div>
<Btn increment={(e) => this.increment()} />
</div>
);
}
increment() {
this.setState({
counter: this.state.counter + 1,
});
}
}
非父子组件数据的共享
有时候我们需要跨多个层级进行数据共享,但是一直通过props
逐层传递就会出现中间的冗余。
我们可以使用React
的Context
方法。
Context
是为了共享那些对于一个组件树而言是"全局"的数据,例如当前认证的用户、主题或首选语言等。
我们首先需要创建一个共享的Context
对象,我们可以在createContext()函数中传入一个对象设置默认值。
//创建context对象,类组件获取context的方式
const ProfileContext = React.createContext();
const ThemeContext = React.createContext();
当组件所处的树中没有匹配到 Provider
时,其 defaultValue
参数才会生效。此默认值有助于在不使用 Provider
包装组件的情况下对组件进行测试。注意:将 undefined
传递给 Provider
的 value
时,消费组件的 defaultValue
不会生效。
export default class Con extends Component {
constructor(props) {
super(props);
this.state = {
nickname: "zhangsan",
level: 1991,
};
}
render() {
return (
<div>
<ProfileContext.Provider value={this.state}>
<ThemeContext.Provider value={{ color: "red" }}>
<Profile />
</ThemeContext.Provider>
</ProfileContext.Provider>
</div>
);
}
}
function Profile(props) {
return (
<div>
{/* <ProfileHeader /> */}
<FProfileHeader />
<ul>
<li>设置1</li>
<li>设置2</li>
<li>设置3</li>
<li>设置4</li>
</ul>
</div>
);
}
后代元素接收数据,类和函数组件都可以通过Consumer
方式获取。
function FProfileHeader() {
return (
<div>
<ProfileContext.Consumer>
{(value) => {
return (
<div>
<ThemeContext.Consumer>
{(theme) => {
return (
<div>
<h2 style={{ color: theme.color }}>
用户等级:{value.nickname}
</h2>
<h2>用户昵称:{value.level}</h2>
</div>
);
}}
</ThemeContext.Consumer>
</div>
);
}}
</ProfileContext.Consumer>
</div>
);
}
另一种获取数据方式,只有类组件可以使用:
class ProfileHeader extends Component {
//声明接收context后就可以使用this.context来获取了
static contextType = ProfileContext;
render() {
return (
<div>
<h2>用户等级:{this.context.level}</h2>
<h2>用户昵称:{this.context.nickname}</h2>
</div>
);
}
}
事件总线
事件总线其实就是利用发布订阅方式进行跨组件传递,这里推荐两个库:events
和pubsub-js
,这两个库都可以用作传递数据,这里就以events来做案例。
安装方式:
//npm方式
npm install events -S
//yarn方式
yarn add events
使用方式:
import React, { PureComponent } from "react";
//引入
import { EventEmitter } from "events";
//创建事件
const event = new EventEmitter();
export default class Home extends PureComponent {
render() {
return (
<div>
我是home组件
<Profile />
</div>
);
}
//一般在这个函数中执行事件监听
componentDidMount(){
event.on('test1', (arg)=>{
console.log(arg);
})
}
//取消监听
componentWillUnmount(){
event.removeListener('testt1');
}
}
class Profile extends PureComponent {
render() {
return <div onClick={e=>this.send()}>我是profile组件</div>;
}
send(){
event.emit('test1', {name:12, age:33});
}
}
React中实现vue的slot
React
中没有像Vue
中提供的slot
插槽相关的API,但是灵活的JSX属性可以实现类似的功能。
方式1:
import Nav from "./nav";
function App() {
return (
<div className="App">
<Nav>
<div>left</div>
<div>center</div>
<div>right</div>
</Nav>
</div>
);
}
export default App;
卸载组件中的标签会以props
的形式传递给组件,组件中获取方式如下:
import React, { Component } from "react";
import "./nav.css";
export default class componentName extends Component {
render() {
const { children } = this.props;
return (
<div className="nav">
<div className="nav-left">{children[0]}</div>
<div className="nav-center"> {children[1]}</div>
<div className="nav-right"> {children[2]}</div>
</div>
);
}
}
这种方式可以实现slot
的效果,但是还是不够灵活,组件内必须按照固定的位置来渲染组件。
方式2:
import Nav2 from "./nav2";
function App() {
/* 两种方式传递组件,类似slot */
return (
<div className="App">
<Nav2
leftSlot={<div>left</div>}
centerSlot={<div>center</div>}
/* href如果只写#可能会触发eslint,要避免出发警告可以加上个 / */
rightSlot={<a href="/#">right</a>}
/>
</div>
);
}
export default App;
通过属性传值方式就可以在外部控制指定位置的插槽了,组件中接收方式如下:
import React, { Component } from "react";
import "./nav.css";
export default class componentName extends Component {
render() {
const { leftSlot, centerSlot, rightSlot } = this.props;
return (
<div className="nav">
<div className="nav-left">{leftSlot}</div>
<div className="nav-center"> {rightSlot}</div>
<div className="nav-right"> {centerSlot}</div>
</div>
);
}
}
这种方式在外部就可以控制插槽的位置,类似于vue
中的具名插槽。
PureComponent
当我们在state
中添加一个属性,但是我们并没有在jsx
中使用这个属性,即视图并没有依赖这个数据,我们就不用更新视图,但是React
只要监听到state
的改变就会重新调用render
。另外如果子元素也没有使用父元素的state
那么也不用更新,但是只要父元素执行了render
函数,其子元素的所有的render
函数都会执行,这些更新是完全没有必要。我们可以通过shouldComponentUpdate
来控制组件的更新。
该函数默认返回true
,即需要调用render
函数,我们可以比较新旧props
来判断是否需要更新,即返回false
就不更新。当然React
是想到了这个问题,所以提供了一个组件PureComponent
。我们书写组件如果需要判断是否更新就可以继承这个组件
export default class Con extends PureComponent {
//...
}
该组件重写了shouldComponentUpdate
这个方法,默认对新旧的state
或者props
进行浅比较,判断是否更新。所以我们不需要自己实现比较函数,当然React
也不推荐我们自行实现深比较函数,因为深比较也会消耗性能,这是不必要的。
React
通过该比较函数判断是否需要更新
function shallowEqual(objA: mixed, objB: mixed): boolean {
if (is(objA, objB)) {
return true;
}
if (
typeof objA !== 'object' ||
objA === null ||
typeof objB !== 'object' ||
objB === null
) {
return false;
}
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
if (keysA.length !== keysB.length) {
return false;
}
// Test for A's keys different from B.
for (let i = 0; i < keysA.length; i++) {
if (
!hasOwnProperty.call(objB, keysA[i]) ||
!is(objA[keysA[i]], objB[keysA[i]])
) {
return false;
}
}
return true;
}
高阶组件memo
我们之前处理组件更新问题对于类组件时可以使用PureCompoent
,但是对于函数式组件我们就可以使用memo
这个高阶组件来处理。
我们可以使用这个高阶组件来包裹一个函数式组件返回一个组件。
import React, { PureComponent, memo } from "react";
const Fcn = memo(function () {
console.log("函数组件重新调用");
return <div>函数组件</div>;
});
这样该函数就和继承了PureComponent
的类式组件一样了。
ref
React
的原生开发中也不建议直接操作DOM,但有时我们确实获取DOM可以通过ref
方式。
在React
中使用ref
有三种方式:
字符串方式
<h2 ref="dom">你好</h2>
console.log(this.refs.dom);
这种方式已经过时,官方不推荐使用了
对象形式
首先创建一个ref
对象
import React, { Component, createRef } from "react";
export default class Con extends Component {
constructor(props) {
super(props);
this.dom = createRef();
}
}
render() {
return (
<div>
<h2 ref={this.dom1}>世界</h2>
</div>
);
}
获取方式:
console.log(this.dom1.current);
函数形式
传入一个回调函数,这个函数会传入一个元素对象,我们定义属性接收就可以。
<h2 ref={(c) => (this.dom2 = c)}>哈哈</h2>
console.log(this.dom2)
推荐方式二三都可以,但是要注意上述方式都只适用于类组件,函数组件需要通过React.forwardRef
方式获取。
ref的转发
之前说的方式都只适用于类式组件,而函数时组件可以通过React中自带的一个高阶函数(forwardRef
)处理来使用ref
。
import React, { PureComponent, createRef, forwardRef } from "react";
const Profile = forwardRef(function (props, ref) {
/* 通过该高阶函数增强,多了一个参数ref,此时将ref放在指定元素上就可以获取对应dom 了 */
return <p ref={ref}>我是函数组件</p>;
});
export default class App extends PureComponent {
constructor(props) {
super(props);
this.profileRef = createRef();
}
render() {
return (
<div>
{/* 要注意ref不是属性,和props不一样this,该属性是react内部管理 */}
<Profile ref={this.profileRef} />
<button onClick={(e) => this.getRef()}>获取ref</button>
</div>
);
}
getRef() {
console.log(this.profileRef.current);
}
}
运行结果:
受控组件和非受控组件
在HTML中表单元素(input
,textarea
,select
等)通常自己维护state
,并且根据用户输入进行更新。而在React
中可变状态通常保存在state
中来进行更新维护。我们可以把两者结合起来,使 React
的 state
成为"唯一数据源"。渲染表单的 React
组件还控制着用户输入过程中表单发生的操作。被 React
以这种方式控制取值的表单输入元素就叫做"受控组件"。其实可以理解为vue中对于表单数据的双向绑定(v-model)
。
import React, { Component } from "react";
export default class Fcn extends Component {
constructor(props) {
super(props);
this.state = {
username: "zhangsan",
fruist: "pear",
};
}
render() {
return (
<div>
<form onSubmit={(e) => this.handleSubmit(e)}>
<label htmlFor="username">
用户:
<input
id="username"
type="text"
value={this.state.username}
onChange={this.gethandleChange("username")}
></input>
{/* react中select加上value可设置默认值,原生的是在option标签上加selected */}
<select
name="fruits"
onChange={this.gethandleChange("fruist")}
value={this.state.fruist}
>
<option value="apple">苹果</option>
<option value="banana">香蕉</option>
<option value="pear">梨子</option>
</select>
</label>
<input type="submit" value="提交" />
</form>
</div>
);
}
handleSubmit(e) {
//取消默认行为
e.preventDefault();
console.log(this.state.username);
console.log(this.state.fruist);
}
gethandleChange(valueType) {
return (e) => {
this.setState({
//计算属性名获取属性名称,将多种输入框的受控函数合并
[valueType]: e.target.value,
});
};
}
}
当然表单数据将交由 DOM 节点本身来处理就称为非受控组件。一般还是推荐使用受控组件。
Portals的使用
我们所写的DOM元素都是挂载在id
为root
的根元素上,但是有些DOM结构我们希望它独立的挂载到父元素或者根元素以外的地方,那么就可以使用Portals
这个API。
在父组件中我们希望将Modal
这个组件中的子元素渲染到指定id的元素内。
class Home extends Component {
render() {
return (
<h2>
home
<Modal>
{/* 需求,将所有的Modal中的子组件渲染到屏幕中间中去 */}
<h2>modal</h2>
</Modal>
</h2>
);
}
}
我们可以在Modal
组件中使用这个api
class Modal extends Component {
render() {
/* 将子组件挂载到#modal, */
return ReactDOM.createPortal(
this.props.children,
document.getElementById("modal")
);
}
}
public/index.html
中设置
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<div id="modal"></div>
</body>
渲染结果:
fragment的使用
我们在书写jsx
语法是,返回的DOM元素是必须要一个跟标签包裹,比如div
,但是这个div
除了满足语法外没有任何的作用,所以使用fragment
就可以解决这个问题。
这个标签使用React内部自带
import React,{ Fragment } from "react";
function foo() {
return (
<Fragment>
<div></div>
<input/>
</Fragment>
)
}
该标签并不会被渲染成任何元素,如果不加key
属性可以简写为<></>
,如果加上key
就需要完整的写法。
StrictMode
这个组件不会被渲任何元素,主要作用为检查子元素的潜在问题。类似JS的严格模式。
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
主要检测内部的子元素是否使用了不安全的生命周期,过时的refAPI
和一些废弃的方法和API。还可以检测意外的副作用,比如组件的constructor
就会执行两次来确定是否产生副作用。