React-基础总结
react 是什么?
React是一个声明式,高效且灵活的用于构建用户界面的JavaScript库;
使用React可以将一些简短,独立的代码片组合成复杂的UI界面,这些代码片段就是组件
- 一个用于构建用户界面的 js 库
- 一个将数据渲染为 HTML 视图的开源 js 库
react 由 Facebook 开发且开源
为什么要学?
- 原生 js 操作 DOM 繁琐,效率低(DOM API 操作 UI)
- 使用 js 直接操作 DOM,浏览器会进行大量的重绘重排
- 原生 js 没有组件化编码方案,代码重用率低
react 特点
- 采用 组件化 模式,声明式编码,提高开发效率和组件复用率
- 在 ReactNative 中可以使用 React 语法进行移动端开发
- 使用 虚拟DOM + 优秀的 Diffing 算法,尽量减少与真实 DOM 的交互
环境搭建
- 全局安装
create-react-app
:npm install -g create-react-app
- 使用脚手架创建 react 项目:
create-react-app react-demo
虚拟 DOM
- 本质就是一个 Object 类型的对象
- 虚拟 DOM 比较轻,真实 DOM 比较重。因为虚拟 DOM 是在 React 内部使用,不需要真实 DOM 上那么多的属性
- 虚拟 DOM 最终会被 React 编译为真实 DOM,呈现在页面上
JSX 语法 (javascript XML 的简写)
JSX 是 js 的语法扩展,用来描述 UI;在编译之后,JSX表达式会被转换为普通的 js 函数,并且对其取值后得到 js 对象
语法规则:
- 定义虚拟DOM时,不能写引号,写引号就成了字符串
- JSX 也是一个表达式,这意味着:
- 在 if 或者 for 中使用 JSX
- 将 JSX 赋值给一个变量
- 把 JSX 做为参数传入
- 从函数中返回 JSX
- 标签中混入JS表达式时要用
{}
;指定属性时使用引号(单引号或双引号)则为字符串,如果使用大括号,则表明插入的是一个js表达式 - 样式的类名指定不要用 class,要用
className
,因为 class 是 es6 的关键字 - 内联样式要用
style={{key: value}}
的形式去写;且 css 属性以 - 连接的要转换为小驼峰式写法 - 虚拟 DOM 必须只能有一个根标签
- 标签必须闭合
- 标签首字母
- 若小写字母开头,则将该标签转为html中同名的标签,若html中无该标签对应的同名元素,则报错
- 若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错
表达式和语句的区别
表达式会产生一个值,可以放在任何一个需要值的地方,可以使用变量接收;语句就是一段代码
模块,组件,模块化,组件化
模块:向外提供特定功能的 js 程序,一般就是一个 js 程序;模块的作用是复用 js
组件:相对于界面来说,用来实现局部功能效果的代码和资源的集合(html/css/js/images);将 UI 拆分为独立可复用的代码片段,并对每个片段进行构思
react 组件
定义组件的两种方式:函数式组件和类式组件
组件名必须首字母大写,否则会被当成 HTML 标签
函数式组件
函数式组件:接收唯一带有数据的 props(代表属性),并且返回一个 React 元素
函数式组件适用于无状态的简单组件
注意点:
- 函数式组件中 this 为 undefined,因为由 babel 转移后的函数开启了严格模式
- 函数组件接收唯一参数
props
,但不能使用state
和refs
function Demo() {
// 此处的this是undefined,因为babel翻译过后,开启了严格模式
console.log(this)
return <h2>我是用函数定义的组件,示用于【简单组件】的定义</h2>
}
类式组件 class
顾名思义,就是使用类 class
来定义的组件
class 组件适用于有状态的复杂组件
当React元素为用户自定义组件时,它会将JSX所接受的属性以及子组件(children)转换为单个对象传递给组件,这个对象称之为props
注意点:
- 必须继承自
React.Component
或者React.PureComponent
- 组件名必须首字母大写,因为首字母小写的会被当成是原生 DOM 标签
- 必须重写
render
函数,且render
函数必须有返回值(返回值可以是jsx
) - 所有 React 组件都必须像纯函数1一样保护它们的 props 不被修改
class MyComponent extends React.Component {
// 构造函数 constructor 只会在初始化组件时执行一遍
constructor(props) {
// 在使用 this 之前必须调用 super()
super(props)
}
// render 函数会在初始化组件时执行一遍;且在每次状态发生改变时都会重新执行
render() {
// render 函数内的 this 指向当前组件实例
console.log(this)
// render 必须要有返回值,如无返回值则会报错
return <h2>我是用类式组件,示用于【复杂组件】的定义</h2>
}
}
组件实例的三大属性
组件实例上有三大属性:state
,props
,refs
注意:函数组件
无实例,所有没有此属性
组件的状态 state
组件的 state
是组件的状态,完全受控于组件;
组件被称为状态机,通过更新组件的 state 来更新页面对应的页面显示(重新渲染组件)
定义 state 的两种方式:
// 第一种方式,在 constructor 体内,重新定义 state
class MyComponet extends React.Component {
constructor(props) {
super(props);
this.state = {
isHot: false
}
}
render() {
return (<div></div>)
}
}
// 第二种方式,直接在类内部定义 state
class MyComponet01 extends React.Component {
state = {
isHot: false
}
// 实际工作中一般会将函数定义为 箭头函数,好处是不用重新定义 this,且可直接使用
changeIsHot = () => {
const { isHot } = this.state
this.setState({
isHot: !isHot
})
}
render() {
return (<div></div>)
}
}
使用 setState
更改状态
当我们定义了 state 之后,可使用 setState
方法来更改 state
使用的两种方式:
- 对象式的 setState: setState(stateChange, [callback])
- stateChange 是状态改变对象, 该对象可体现出状态的更改
- callback 是可选的回调函数, 它在状态更新完毕, 界面也更新完毕(render 被调用完)后才会被调用
- 函数式的 setState: setState(updater, [callback])
- updater 是 接收原 state 和 props 两个参数的函数
- updater 返回值是 stateChange 对象的函数
- callback 是可选函数, 它在状态更新完毕, 界面也更新完毕(render 被调用完)后才会被调用
对象式的 setState 是函数式 setState 的简写方式; 如果状态依赖于原 state, 可使用函数式的 setState; 如果状态不依赖于原 state, 可使用对象式的 setState; 如果需要获取 setState() 执行后的最新状态数据, 需要在第二个参数 callback 中来获取
更改 state
注意事项:
-
不能直接更改
state
(因为不会触发组件更新),应该使用setState()
-
setState()
的更新会被合并,当setState()
时,React 会将对象合并到当前 state -
state
的更新是异步更新的,所以不能依赖它们的值来更新下一个状态。想解决setState()
异步更新的问题,可以让setState()
接收一个函数而不是一个对象,这个函数用上一个 state 做为第一个参数,将此次更新被应用时的 props 做为第二个参数this.setState((state, props) => {})
-
如果调用多次 setState 来更改同个状态,将会合并为一个,且第一次的生效;处于性能考虑,React 可能会把多个 setState 合并为一个调用
更改状态的两种方式:
// 第一种,在 constructor 内改变方法的 this 指向
class Demo extends React.Component {
constructor(props) {
// super(props) 必须在使用 this 之前被调用
super(props)
this.state = {
isHot: true
}
// 在constructor内部可改变方法的 this 指向
this.changeWeather = this.changeWeather.bind(this)
}
changeWeather() {
// 默认这里的 this 为 undefined,
// 因为此方法是做为事件的,并不是由组件实例来调用的;且 class 内方法内部默认开启了严格模式
this.setState({
isHot: !this.state.isHot
})
}
render() {
const { isHot } = this.state
return (
<div>
<h2>这天气好 { isHot ? '炎热' : '凉爽' }</h2>
<button onClick={ this.changeWeather }>改变天气</button>
</div>
)
}
}
ReactDOM.render(<Demo />, document.getElementById('root'))
第二种,箭头函数,推荐写法
class Demo extends React.Component {
state = {
isHot: true
}
changeWeather = () => {
this.setState({
isHot: !this.state.isHot
})
}
render() {
const { isHot } = this.state
return (
<div>
<h2>这天气好 { isHot ? '炎热' : '凉爽' }</h2>
<button onClick={ this.changeWeather }>改变天气</button>
</div>
)
}
}
ReactDOM.render(<Demo />, document.getElementById('root'))
setState 异步更新的测试
class Demo extends React.Component {
state = {
isHot: true
}
changeWeather = () => {
// 更新了一次
this.setState({
isHot: !this.state.isHot
})
// 想要更新第二次
// 这里并不能立即获取到 state 更新后的值,可以看出 setState 是异步更新的
// this.setState({
// isHot: !this.state.isHot
// })
// 想要解决 setState 是异步更新且不能立即获取到更新后的值的问题,可以使用回调函数的方式
this.setState((state, props) => {
// 这里可获取到上个 state 更新后的值
console.log('第一次:', state, props)
return {
// 我再这里可立即获得 状态更新后的值
isHot: !state.isHot
}
})
this.setState((state, props) => {
// 这里可获取到上个 state 更新后的值
console.log('第二次:', state, props)
return {
// 我再这里可立即获得 状态更新后的值
isHot: !state.isHot
}
})
}
render() {
const { isHot } = this.state
return (
<div>
<h2>这天气好 { isHot ? '炎热' : '凉爽' }</h2>
<button onClick={ this.changeWeather }>改变天气</button>
</div>
)
}
}
ReactDOM.render(<Demo />, document.getElementById('root'))
组件的属性 props
组件的 props
可以接收组件使用方传递过来的参数;组件标签的所有属性都保存在对象实例的 props 属性内
注意点:
- 在组件内部 props 是只读的,不能修改 props 的值
基本使用
// 函数组件中接收参数 props,但不能使用 state 和 refs
function Demo(props) {
return <h1>我是函数式组件</h1>
}
ReactDOM.render(<Demo name="dname" age={18} say={()=>{}} />, document.getElementById('root'))
// 类式组件中使用 this(当前组件实例),可获取组件使用方传递的 props(尤其是 render 函数内)
class MyComponent extends React.Component {
//
constructor(props) {
super(props)
}
render() {
// 可直接通过 this 来获取组件实例的 props
console.log(this.props)
return <h1>我是类式组件</h1>
}
}
ReactDOM.render(<MyComponent name="mname" age={20} say={()=>{}} />, document.getElementById('root'))
批量传递 props
在 react,JSX 标签中可以直接使用对象展开符来传递属性 props
class Info extends React.Componet {
render() {
console.log(this)
return (
<ul>
<li>姓名: {this.props.name}</li>
<li>年龄: {this.props.age}</li>
<li>学历: {this.props.edu}</li>
</ul>
)
}
}
const p = {name: 'lishihao', age: 25, edu: '大专'};
ReacDOM.render(
<Info {...p} />,
document.getElementById('app')
)
使用 propTypes 给传入的 props 做限制和使用 defaultProps 给传入的 props 设置默认值
- 函数组件时,可在函数上添加静态属性
propTypes
和defaultProps
来分别给组件做_类型/是否必须的限制_和_属性的默认值_ - 类式组件时,可在类上添加静态属性
propTypes
和defaultProps
来分别给组件做_类型/是否必须的限制_和_属性的默认值_
示例:
// 首先需要引入 PropTypes 模块
// 函数组件
function Demo(props) {
return <h1>函数组件:{ `${props.name}, ${props.age},${props.say()}` }</h1>
}
Demo.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
// 注意这里 function,需要写成 func
say: PropTypes.func
}
// 对不是必须的属性设置默认值
Demo.defualtProps = {
age: 18,
say: () => {}
}
// 类式组件
// 对props进行限制
// 1. 引入PropTypes,
// 2. 定义组件
// class MyProps extends React.Component {
// render() {
// console.log(this);
// const {name, age, sex} = this.props;
// return (
// <ul>
// <li>姓名:{name}</li>
// <li>年龄:{age}</li>
// <li>性别:{sex}</li>
// </ul>
// );
// }
// }
// MyProps.propTypes = {
// // 字符串类型并且是必须的
// name: PropTypes.string.isRequired,
// sex: PropTypes.string,
// age: PropTypes.number,
// }
// MyProps.defaultProps = {
// // 如果未传sex, 则使用默认值
// sex: '不难不女'
// }
// 简化
class MyProps extends React.Component {
render() {
console.log(this);
const {name, age, sex} = this.props;
return (
<ul>
<li>姓名:{name}</li>
<li>年龄:{age}</li>
<li>性别:{sex}</li>
</ul>
);
}
// 在开发中尽可能将关于组件的一切都写在组件内部
static propTypes = {
// 字符串类型并且是必须的
name: PropTypes.string.isRequired,
sex: PropTypes.string,
age: PropTypes.number,
}
static defaultProps = {
// 如果未传sex, 则使用默认值
sex: '不难不女'
}
}
const per = {name: 'lishihoa', age: 12, sex: '男'}
ReactDOM.render(<MyProps {...per}/>, '容器');
构造器函数以及构造器函数中的 props
对于类式组件来说,构造器函数 constructor()
,会在组件挂载之前,创建组件实例时被调用。
构造器函数一般两个作用(如果不需要以下,则可以不写 constructor):
- 初始化 state
- 为事件处理函数绑定实例
如果在构造器函数内(constructor)在使用 this
之前,必须先调用 super()
;调用 super()
时需要传入 props
,也就是执行 super(props)
如果不传入 props
,则在之后(构造器函数之中)使用 this.props
可能为 undefined
其他注意事项:
- 在
constructor()
函数中不要调用 setState() 方法;如果组件需要内部 state,可直接赋值给 state,也就是说只能在构造器函数中直接为this.state
赋值 - 避免将
props
的值复制给state
(第一毫无必要,第二还会产生bug:更新prop中的值时不会影响到state)
组件的属性 refs
Refs
可以允许我们访问 DOM 元素或在 render 方法中创建的 React 元素;比如管理焦点,文本选择,媒体播放和触发强制动画都可使用 refs
注意:不能在函数组件上使用 ref,因为函数组件没有实例
创建 ref 的三种方式:
- 字符串 ref,(不推荐,有性能问题)
- 回调函数 ref
React.createRef()
方式
字符串 ref(已弃用,不推荐)
顾名思义,就是直接在 jsx 元素或者 DOM 元素上添加 ref
属性,且 ref 的属性值为一个字符串
字符串 ref 会引发一些效率上的问题,具体问题请参阅:react.docschina.org/docs/refs-a…
class MyComponent extends React.Component {
// 写成箭头函数的好处是,不需要再为函数 this 绑定当前组件实例
onBtnClick = (e) => {
console.log(this)
// 第二步:直接使用 this.refs 就可以获取字符串 ref
console.log(this.refs.inputRef)
}
render() {
return (
<div>
{ /* 第一步:添加 ref,值为一个字符串 */ }
<input ref="inputRef" placeholder="点击右侧按钮获取我的值" />
<button onClick={this.onBtnClick}>点击获取值</button>
</div>
)
}
}
ReactDOM.render(<MyComponent />, document.getElementById('root'))
回调 ref(推荐)
顾名思义,ref
值为一个回调函数,此回调函数的参数就是想要获取的元素
内联函数
内联回调函数会有个问题,就是在初始化时执行一次,之后每次元素更新时会被调用两次:第一次参数为 null,第二次才是实际的想要获取的元素。这是因为在每次渲染时都会创建一个新的函数实例,所以 React 清空旧的 ref 并设置新的;通过将 ref 的回调函数定义成 class 的绑定函数的方式可解决此问题
class Demo01 extends React.Component {
state = {
count: 0
}
onBtnClick = (e) => {
const { count } = this.state
console.log('更新前count: ', count, this.currentNode, this.currentNode.value)
this.setState({
count: count+1
})
}
inputRefCb = (curNode) => {
console.log('inputRefCb: ', curNode)
this.currentNode = curNode
}
render() {
const { count } = this.state
return (
<div>
<input type="text" placeholder="请输入值" ref={this.inputRefCb} value={this.state.count} readOnly/>
<button onClick={this.onBtnClick}>更新 count: {count}</button>
</div>
)
}
}
ReactDOM.render(<Demo01 />, document.getElementById('root02'))
React.createRef()
使用步骤:
- 使用
React.createRef()
来创建一个 ref 容器 - 将
ref
属性附加到 React 元素 - 当 ref 被传递给 render 中的元素时,对该节点的引用可以在 ref 容器的
current
属性中访问到
ref 的值:
- 当 ref 属性用于 html 元素时,构造函数中使用
React.createRef()
创建的 ref 接收底层的 DOM 元素做为其current
属性 - 当 ref 属性用于自定义 class 组件时,ref 对象接收组件的挂在实例做为其 current 属性
- 不能在函数组件上使用 ref 属性,因为函数组件没有实例
class Demo extends React.Component {
// 第一步,使用 React.createRef() 创建一个 ref 容器
inputRef = React.createRef()
onBtnClick = (e) => {
// 第三步,使用 this.inputRef.current 来获取想要的元素
console.log('按钮点击后,输入框的值为:', this, this.inputRef, this.inputRef.current.value)
}
render() {
return (
<div>
{ /* 第二步,在想要获取的元素上写上 ref 属性,属性值为 React.createRef 创建的容器 */ }
<input type="text" placeholder="请输入值" ref={this.inputRef} />
<button onClick={this.onBtnClick}>获取输入框的值</button>
</div>
)
}
}
ReactDOM.render(<Demo />, document.getElementById('root'))
组件的生命周期
组件在创建,更新,卸载过程中会执行一些特定的方法,这些方法就是生命周期方法,我们可以重写这些方法来在特定的阶段执行一些自定义的功能,比如在组件挂载后开启定时器,ajax 请求、在组件卸载时清空定时器等
class Count extends React.Component {
constructor(props) {
super(props)
console.log('Count---constructor')
}
state = {
count: 0,
other: '其他属性'
}
/**
* 必须:
* 1. static 方法
* 2. 必须有返回值,返回值是 state 对象或者为 null
*
* 常用于 state 的值在任何时候都取决于 props
*/
static getDerivedStateFromProps(props, state) {
console.log('Count---getDirevedStateFromProps');
return null
// return props
}
componentDidMount() {
console.log('Count---componentDidMount');
}
shouldComponentUpdate() {
console.log('Count---shouldComponentUpdate');
return true
}
/*
getSnapshotBeforeUpdate 在最近一次渲染输出(提交到 DOM 节点)之前调用,
它使得组件能在发生更改之前从 DOM 中获取一些信息(滚动位置等),
此生命周期的任何返回值都将作为参数(第三个参数)传递给 componentDidUpdate()
*/
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log('Count---getSnapshotBeforeUpdate');
// return null
// 在更新前使用快照,返回值作为copmonentDidUpdate 的第三个参数
return 'lsh'
}
componentDidUpdate(preProps, preState, snapshotValue) {
console.log('Count---componentDidUpdate', preProps, preState, snapshotValue);
}
handleAdd = () => {
console.log('this.state:', this.state)
this.setState({
count: this.state.count+1
})
}
render() {
console.log('Count---render', this);
return (
<div>
<span>当前值为{this.state.count}</span>
<button onClick={this.handleAdd}>点击加1</button>
</div>
)
}
}
事件处理
- React 中的事件的命名采用小驼峰式,而不是纯小写;比如
onclick
在 react 中是onClick
- 使用 JSX 语法时,需要传入一个函数做为事件处理函数,而不是一个字符串
注意点:
- 不能通过
return false
的方式来阻止默认事件,而必须显式调用preventDefault
- React 中的事件对象是一个
合成事件(SyntheticEvent)
;合成事件是浏览器原生事件的跨浏览器包装器,除了兼容所有浏览器外,还拥有和浏览器原生事件相同的接口,包括stopPropagation()
和preventDefault()
,可使用nativeEvent
属性来获取原生事件
给事件函数传参数:
- 使用箭头函数
<button onClick={(e) => this.handleClick(params, e)}>点击传参</button>
- 借用
Function.prototype.bind()
函数// 事件对象 e 会被默认做为最后一个参数传进去 <button onClick={this.handleClick.bind(this, params)}>点击传参</button>
- 使用函数柯里化方式
class Demo extends React.Component { handleClick = (params) => { return function(e) { // 实际的回调函数处理 console.log(params, e) } } render() { // 意思是将 handleClick 的函数调用结果做为事件处理函数 return <button onClick={this.handleClick(params)}>点击传参</button> } }
表单数据
在 HTML 中,表单元素(input,textarea,select)通常维护自己的 state,并根据用户输入进行更新,而在 React 中,可变状态(mutable state)通常保存在组件的 state 中,并只能通过 setState 来更新
两种类型:
- 受控组件
- 非受控组件
受控组件
使 React 的 state
称为唯一数据源,渲染表单的 React 组件还控制着用户输入过程中表单发生的操作,被 React 以这种方式控制取值的 表单输入元素
就叫做 受控组件
class Demo extends React.Component{
state = {
username: '',
password: ''
}
onChange = (dataType) => {
return (e) => {
this.setState({
[dataType]: e.target.value
})
}
}
handleClick = () => {
const { username, password } = this.state
console.log(`提交了:${username};${password}`)
}
render() {
return (
<div>
<input type="text" value={this.state.username} onChange={this.onChange('username')} />
<input type="text" value={this.state.password} onChange={this.onChange('password')} />
<button onClick={this.handleClick}>点击提交</button>
{/* 如果将,value 的值设置为 null 或者 undefined 则还是可输入;但如果是固定值,则会阻止用户输入
<input type="text" value="hi" />
*/}
</div>
)
}
}
非受控组件
在一个受控组件中,表单数据是由 React 组件来管理的;另一种方案是使用 非受控组件
,这时表单数据将由 DOM 节点来处理
要编写一个非受控组件,不需要为每个状态更新都编写数据处理函数,可以 使用 ref
从 DOM 节点中获取表单数据
非受控组件,可赋默认值:<input type="checkbox" />
和 <input type="radio" />
支持 defaultChecked
;<select>
,<textarea>
和 <input type="text" />
支持 defaultValue
class Demo extends React.Component {
textRef = React.createRef()
onSubmit = (e) => {
// 不能使用 return false 阻止默认事件的提交
// return false;
// 只能使用 e.preventDefault() 阻止默认事件的提交
e.preventDefault()
// 提交事件
console.log('onSubmit: ', e, this.textRef, this.hobbyRef)
}
render() {
return (
<div>
<form action="#" onSubmit={this.onSubmit}>
<label>
{/* 可使用 defaultValue 来给 input 指定默认值 */}
Name: <input type="text" defaultValue="123456" ref={this.textRef} name="user" />
</label>
Hobby:
<label><input type="checkbox" defaultChecked name="hobby" value="1" />篮球</label>
<label><input type="checkbox" name="hobby" value="2" />足球</label>
<label><input type="checkbox" name="hobby" value="3" />乒乓球</label>
<input type="submit" value="Submit" />
</form>
</div>
)
}
}
组件懒加载(lazyLoad)
一般用于路由组件
使用方式:
-
通过 react 的 lazyLoad 函数配合 import() 函数动态加载路由组件:
const Home = lazy(() => import('@/pages/Home'))
-
通过 <Suspense> 指定在加载到路由打包文件显示前显示的一个自定义 loading 界面
<Suspense fallback={\<h2\>loading...\</h2\>}> <Switch> <Route path="/xxx" component={xxx} /> <Redirect to="/home" /> </Switch> </Suspense>
学习视频链接
- 千锋 react:www.bilibili.com/video/BV1dP…
- 禹神(非常细):www.bilibili.com/video/BV1wy…
- 珠峰 react (细又深):www.bilibili.com/video/BV1sx…
Footnotes
-
纯函数即不会更改入参,并且多次调用相同的入参都会返回相同的结果 ↩
转载自:https://juejin.cn/post/7243725952789643301