为什么react选择使用jsx(JavaScript Extension)
react认为渲染逻辑和ui处理逻辑存在高度的耦合,比如页面的点击事件需要绑定js,数据的动态处理和更新需要js处理。所以选择使用jsx,认为渲染逻辑和ui处理逻辑两者是密不可分的。在js中处理渲染和ui相关的东西。
jsx只能有一个根元素
因为jsx是交给babel的jsx函数处理的时候,是以一种树状的结构来进行处理转换成html代码,树只能有一个根节点。
jsx中可以显示的值
数组、字符串、数字可以直接插入显示,但是当值的类型为null undefind boolean的时候 jsx默认显示为空,也就是不显示出来。对象类型无法直接显示,可以显示js右值表达式(除开对象)。
事件参数传递,传递两三个参数包括默认参数
使用箭头函数,或者利用bind(),但是bind所传递的event是在参数列表的最后
jsx的本质
jsx 仅仅只是 React.createElement()的语法糖,jsx经过babel中的_jsx函数经过转换后,供React.createElement()函数使用(类似于vue中的h函数),来生成虚拟dom(vdom),最终可以运行在各个平台。
// jsx代码
<div>
<button onClick={ () => {this.setState({ isShow: !isShow })} }>切换</button>
{ isShow && <h2>react</h2> }
<h2 style={{display: isShow ? "block" : 'none'}}>react2</h2>
</div>
经过babel转换后的代码
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
/*#__PURE__*/_jsxs("div", {
children: [/*#__PURE__*/_jsx("button", {
onClick: () => {
this.setState({
isShow: !isShow
});
},
children: "\u5207\u6362"
}), isShow && /*#__PURE__*/_jsx("h2", {
children: "react"
}), /*#__PURE__*/_jsx("h2", {
style: {
display: isShow ? "block" : 'none'
},
children: "react2"
})]
});
组件划分
react中类组件、函数式组件、有状态组件、无状态组件、展示型组件以及容器型组件。
- 类组件:
- 组件的名称必须是大写字母开头(无论是类组件还是函数式组件)
- 类组件必须继承自
React.Component - 类组件必须实现
render(),render函数可返回:react元素、数组、数字、布尔类型、portal、fragment、null以及undefined。
- 函数式组件:
- 通过函数所创建的组件,不需要继承任何东西。
- 函数的返回值必须要与类组件中的
render()返回值一样。
- 函数式组件与类组件的区别:
-
函数式组件没有声明周期函数,通过hooks来操作。
-
函数式组件this不能指向组件实例。
-
函数式组件没有内部的state,只能通过hooks来操作。
- 类组件生命周期:
先是类的构造函数
父子通信
- 类组件的父子通信,父传子通过构造函数的props获取所传来的值。对传入的类型进行限制,通过propTypes进行限制类型。需要引入
propTypes
MainBanner.propTypes = {
banner: propTypes.array.isRequired,
title: propTypes.string
}
通过defaultProps 设置默认值,es2022 可以通过static在类中设置 defaultProps
MainBanner.defaultProps = {
banner: [],
title: "默认"
}
- 子传父通信,通过props传递过去的函数进行子传父的通信。 案例:
import {Component} from 'react';
import "./index.css";
export class TabControl extends Component {
constructor(p) {
super(p);
this.state = {
currentIndex: 0
}
}
handleClick(currentIndex) {
this.setState({
currentIndex
})
this.props.tabClick(currentIndex);
}
cb = (item, index) => {
const { currentIndex } = this.state;
return (
<div key={index} onClick={() => this.handleClick(index)} className={ this.getClassName(
currentIndex, index)}>
{ item }
</div>
)
}
getClassName(currentIndex, index) {
return `item ${currentIndex === index ? 'active' : ''}`;
}
render() {
const { titles } = this.props;
return (
<div>
<div className="wrap">
{ titles.map(this.cb) }
</div>
</div>
);
}
}
import {Component} from 'react';
import {TabControl} from './TabControl';
export class App extends Component {
constructor(props) {
super(props);
this.state = {
tabIndex: 0,
titles: ['aa', 'bb', 'cc']
}
}
changeTab(tabIndex) {
this.setState({
tabIndex
})
}
render() {
const { titles, tabIndex } = this.state;
return (
<div>
<TabControl tabClick={(i) => this.changeTab(i)} titles={ titles }/>
<h2>{ titles[tabIndex] }</h2>
</div>
);
}
}
插槽
在react中实现插槽的两种方式
- 组件的
children子元素
import React, {Component} from 'react';
import NaviBar from './nav-bar/NaviBar';
class App extends Component {
render() {
return (
<div>
<NaviBar>
<button>left</button>
<button>center</button>
<button>right</button>
</NaviBar>
</div>
);
}
}
export default App;
import React, {Component} from 'react';
import "./style.css"
class NaviBar extends Component {
render() {
const { children } = this.props
return (
<div className="content">
<div className="left">{ children[0] }</div>
<div className="center">{ children[1] }</div>
<div className="right">{ children[2] }</div>
</div>
);
}
}
NaviBar.propTypes = {
};
export default NaviBar;
props属性传递React元素
import React, {Component} from 'react';
import "../nav-bar/style.css"
class NavBarTwo extends Component {
render() {
const { left, center, right } = this.props
return (
<div className="content">
<div className="left">{ left }</div>
<div className="center">{ center }</div>
<div className="right">{ right }</div>
</div>
);
}
}
export default NavBarTwo;
import React, {Component} from 'react';
import NaviBar from './nav-bar/NaviBar';
import NavBarTwo from './nav-bar-two/NavBarTwo';
class App extends Component {
render() {
return (
<div>
<NaviBar>
<button>left</button>
<button>center</button>
<button>right</button>
</NaviBar>
<NavBarTwo left={<h1>left</h1>} center={<h1>center</h1>} right={<h1>right</h1>}/>
</div>
);
}
}
export default App;
- 作用域插槽,通过props上传递的回掉函数实现
import {Component} from 'react';
import {TabControl} from './TabControl';
export class App extends Component {
constructor(props) {
super(props);
this.state = {
tabIndex: 0,
titles: ['aa', 'bb', 'cc']
}
}
changeTab(tabIndex) {
this.setState({
tabIndex
})
}
render() {
const { titles, tabIndex } = this.state;
return (
<div>
<TabControl itemType={(item) => <h1>{item}</h1>} tabClick={(i) => this.changeTab(i)} titles={ titles }/>
<h2>{ titles[tabIndex] }</h2>
</div>
);
}
}
import {Component} from 'react';
import "./index.css";
export class TabControl extends Component {
constructor(p) {
super(p);
this.state = {
currentIndex: 0
}
}
handleClick(currentIndex) {
this.setState({
currentIndex
})
this.props.tabClick(currentIndex);
}
cb = (item, index) => {
const { currentIndex } = this.state;
const { itemType } = this.props
return (
<div key={index} onClick={() => this.handleClick(index)} className={ `item ${currentIndex === index ? 'active' : ''}`}>
{ itemType(item) }
</div>
)
}
render() {
const { titles } = this.props;
return (
<div>
<div className="wrap">
{ titles.map(this.cb) }
</div>
</div>
);
}
}
setState()
为什么要使用setState函数,因为在react中不像vue一样有数据依赖的追踪,各个细节对于react开发者来说都是可见的,所以当数据改变的时候,要通知react该组件有状态发生了改变,所以要有一个函数去操作执行通知,那么该函数就是setState。setState可以传入对象,回掉函数,第二个参数相当于异步后的then。且setState原理是对新对象和原始对象的合并(使用assign方法),When you call setState(), React merges the object you provide into the current state.
React18之前该函数为什么设计成异步,作者在github中回答道,设计成异步因为两个原因:
- 因为在setState中有render函数,数据更新要渲染UI使得UI上的数据也更新,如果是同步操作 比如说像一个循环中数据被更改了上百次,此时同步更新会产生上百个vdom,去进行diff算法。这是完全损耗性能且没必要的。所以就设计成异步,当一定时间后执行。
- 如果是更改数据同步,会造成父子组件数据不同步刷新,父组件更新了要传递给自组件的数据但是此时没有去执行render那么子组件的数据还是之前的,所以就要将更改数据与render一起执行,render是异步的且异步性能消耗小,所以setState设计成异步。
- 但是在react18之前,在settimeout或原声dom操作中的setstate操作是同步操作交给浏览器操作,react18之后的setstate变成了异步操作加入到队列中进行批处理。但是如果还想在react18中同步操作,可以使用
flushSync。
优化
scu优化,shouldComponentUpdate(nextProp, nextState) 该函数返回布尔值通知componentUpdate是否执行render,可以在该函数中对比两次的状态和所传入的属性,如果没有发生变化手动返回false不进行更新操作,节省性能消耗。在react中state或prop发生改变都会重新渲染。
手动实现太麻烦,在react中可以直接继承purecomponent(纯组件类的继承,进行浅层比较,引用类型比较地址、值类型比较值)、memo(函数式组件的使用)
import React, {Component, PureComponent} from 'react';
import Home from './Home';
class App extends PureComponent {
constructor(props) {
super(props);
this.state = {
msg: "a"
}
}
render() {
console.log("App rerender");
const { msg } = this.state;
return (
<div>
<button onClick={() => this.setState({ msg: "b" })}>change</button>
<Home msg={ msg }></Home>
</div>
);
}
}
export default App;
import React, {memo} from 'react';
const Home = memo(function(prop) {
console.log("Home rerender");
return (
<div>
<h1>{ prop.msg }</h1>
</div>
);
})
export default Home;
受控组件与非受控组件
这常见于表单组件中,因为在react中没有双向绑定,只能自己手动来实现双向绑定。所以当表单组件中给value绑定了属于react的state或props,该组件就变成了受控组件,得额外绑定onchangge事件来实现双向绑定。如果使用defaultValue给组件绑定默认值,那么该html标签还是非受控组件。
import React, {Component, createRef, PureComponent} from 'react';
class App extends PureComponent {
constructor(props) {
super(props);
this.inputRef = createRef();
this.state = {
username: '',
pwd: '',
isAgree: false,
hobbies: [
{name: 'a', text: '唱', checked: false},
{name: 'b', text: '跳', checked: false},
{name: 'c', text: 'rap', checked: false},
],
fruit: [],
intro: "saa"
}
}
componentDidMount() {
this.inputRef.current.addEventListener("input", (e) => { console.log(e); })
}
handleOnSubmit(e) {
e.preventDefault();
console.log(this.state, this.inputRef.current.value);
}
handleInputChange(e) {
this.setState({
[e.target.name]: e.target.value
})
}
handelIsAgreeChange(e) {
this.setState({
isAgree: e.target.checked
})
}
handleHobbiesChange(e, index) {
this.state.hobbies[index].checked = e.target.checked;
this.setState({
hobbies: [... this.state.hobbies]
})
}
handleFruitChange(e) {
this.setState({
fruit: [...e.target.selectedOptions].map(item => item.value)
})
}
render() {
const { username, pwd, isAgree,hobbies, fruit, intro } = this.state;
return (
<form onSubmit={(e) => this.handleOnSubmit(e)}>
<div>
<label htmlFor={username}>
用户
<input type="text" id='username' name='username' value={username}
onChange={(e) => this.handleInputChange(e)}/>
</label>
<label htmlFor={pwd}>
密码
<input type="text" id='pwd' name='pwd' value={pwd}
onChange={(e) => this.handleInputChange(e)}/>
</label>
</div>
{/* 单选 */}
<div>
<label htmlFor="isAgree">
<input type="checkbox" checked={isAgree} onChange={(e) => this.handelIsAgreeChange(e)}/>
是否同意
</label>
</div>
{/* 多选 */}
<div>
<label htmlFor="hobbies">
{
hobbies.map((item, index) => <input key={item.name} type="checkbox" name={item.name} checked={item.checked} onChange={(e) => this.handleHobbiesChange(e, index)}/>)
}
</label>
</div>
<select value={fruit} onChange={(e) => this.handleFruitChange(e)} multiple>
<option value="apple">苹果</option>
<option value="banana">香蕉</option>
</select>
<div>
<input type="text" defaultValue={intro} ref={this.inputRef}/>
</div>
<div>
<button type="submit">提交</button>
</div>
</form>
);
}
}
export default App;
高阶组件
高阶组件是参数为组件,返回值为新组件的函数主要作用对要渲染的组件做拦截和增强。
高阶组件可以优雅的处理react代码,可以实现mixin混入,但是hoc嵌套过多也会造成调试困难。
import React, {Component, PureComponent} from 'react';
function enhanced(C) {
class Com extends PureComponent {
constructor(props) {
super(props);
this.state = {
name: "canoe",
age: 18
}
}
render() {
return <C {...this.state} {...this.props}/>
}
}
return Com
}
const Home = enhanced((props) => {
return <h1>Home: Name{props.name} Age{props.age} {props.msg}</h1>
})
class App extends Component {
render() {
return (
<div>
<Home msg={"hello"}/>
</div>
);
}
}
export default App;
Portal
某些情况下,我们希望渲染的内容独立于父组件,甚至是独立于当前挂载到的DOM元素中(默认都是挂载到id为root的dom元素中)
fragment
在render返回的元素中,都需要一个根的div,如果不需要根div,可以使用fragment进行包裹。简写语法为<></>,如果要添加key不能省略。vue3中不需要根div,源码中也用fragment进行了处理。
css使用
- 内联样式,优点:能隔离、能使用js变量。缺点:无法编写伪元素、伪类等。
- 普通css样式,此种方式为全局的css,容易产生冲突。
- cssmodule,react所推荐的。需要在webpack配置环境中配置,modules:true。react脚手架已经配置好了。
- css in js,
css in js是一种模式,其中css是由js生成而不是在外部文件中定义。由第三方库提供。目前流行的库有styled-compemotionglamorous。css-in-js通过js来为css赋予一些能力,包括类似于css预处理器一样的样式嵌套、函数定义、逻辑复用、动态修改状态等等。虽然css预处理器也具有某些能力,但是动态状态依然是一个不好处理的点。所以,目前可以说css-in-js是react编写css最为受欢迎的一种解决方案。
import React, {Component} from 'react';
import AppWrapper from './style';
class App extends Component {
render() {
return (
<AppWrapper>
<div className="section">
<h2 className="title">title</h2>
<p className="content">content</p>
</div>
<div className="footer">
<p>pronounce</p>
<p>pronounce</p>
</div>
</AppWrapper>
);
}
}
export default App;
import styled from 'styled-components';
const AppWrapper = styled.div`
.section {
border: 1px solid red;
&:hover {
background-color: skyblue;
}
}
`
export default AppWrapper
想跟vue一样动态绑定class处理,可以使用classnames库。