要点:
-
react是单项数据绑定,单项数据流
-
vue是双向数据绑定,单项数据流
-
react中所有的组件都是函数
一、创建 React 应用程序
1. 脚手架搭建
creat-react-app react创建项目的脚手架
npx create-react-app my-react(自定义项目名)
cd my-react
npm run start
成功页面:
2. 文件
- index.js :react入口文件,要引入:
import React from 'react';
import ReactDOM from 'react-dom';
3. 类名
- 类名需要写成className,如果有两个className,后者会替换前者。「与vue不同,vue会自动合并css样式」。不过可以自己手动合并
let cls = 'box' ... <div className={'aaa' + cls}></div> - for 改为 htmlFor
react中,点击checkbox后的label时,也需要选中checkbox。
<input type="checkbox" id="aa"/> <label htmlFor="aa">选择</label> // htmlFor服务于id
4. style行内样式
原生写法:
<h1 style="color:red"></h1>
react 中会报错,正确写法是变量里套个对象
- 连词写成小驼峰形式
<h1 style={{color:'red', fontSize:'20px'}}></h1>
// 或
let sty = {color:'red'}
<h1 style={ sty }></h1>
5.普通对象不能直接作为react的儿子存在
例如查看对象内容,需要转为字符串「JSON.stringify()」
二、核心概念
1. 元素渲染
更新UI界面:ReactDom.render()
2. 组件 & props
(1)组件概念
- 组件都是函数,可分为2类:
-
静态组件(函数式组件) hooks:专门为函数式组件准备的 「为了解决函数式组件没有状态,以及没有钩子函数的问题」
function App(){ return <> <h1> 函数式组件 </h1> </> } -
类组件
class App extends React.Component { render() { return ( <h1>类组件</h1> ) } }
-
- 组件名称必须以大写字母开头(使用时)。
<Welcome name="sara"/> - 组件可以拆分提取/组合
- Props是只读的,所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。
(2)父子组件通信
简单实现父组件给子组件传参,子组件展示li列表; 子组件通过调用父组件传入的方法修改父组件数据。
a)类组件方式
parent.jsx
import React, { Component } from 'react'
import Child from './child'
export default class Parent extends React.Component {
state = {
list: [
{
name: '张三',
age: 13
},
{
name: '李四',
age: 13
}, {
name: '王五',
age: 13
}, {
name: '刘六',
age: 13
}
]
}
// 子组件删除父组件数据
parent_Del(n) {
let temp = [...this.state.list]
temp.splice(n, 1)
this.setState({
list: temp
})
}
render() {
let { list } = this.state
return (
<div>
父组件
<Child data={list} onDel={this.parent_Del.bind(this)} />
</div>
)
}
}
child.jsx
import React, { Component } from 'react'
export default class Child extends React.Component {
child_Del(n) {
this.props.onDel(n)
}
render() {
let { data = [] } = this.props
return (
<div>
子组件
<ul>
{
data.map((item, index) => {
return (
<li key={item.name}>
name: {item.name} <br />
age: {item.age}<br />
<button onClick={this.child_Del.bind(this, index)}>删除</button>
</li>
)
})
}
</ul>
</div>
)
}
}
b)函数式组件方式
parent.jsx
function Parent() {
let data = [
{
name: '张三',
age: 13
},
{
name: '李四',
age: 13
}, {
name: '王五',
age: 13
}, {
name: '刘六',
age: 13
}
]
const [list, setList] = useState(data)
const parent_Del = (n) => {
let temp = [...list]
temp.splice(n, 1)
setList(temp)
}
return (
<div>
父组件
<Child data={list} onDel={parent_Del} />
</div>
)
}
export default Parent
child.jsx
function Child(props) {
let { data = [] } = props
const child_Del = (n) => {
props.onDel(n)
}
return (
<div>
子组件
<ul>
{
data.map((item, index) => {
return (
<li key={item.name}>
name: {item.name} <br />
age: {item.age}<br />
<button onClick={() => { child_Del(index) }}>删除</button>
</li>
)
})
}
</ul>
</div>
)
}
export default Child
c)插槽
react中的插槽,通过 children 接收
例如在父组件中写入插槽内容:
<Child data={list}>
<p>这里是插槽</p>
</Child>
子组件:children 接收展示
function Child(props) {
let { data = [], children } = props
return (
<div>
<h2>子组件</h2>
{children}
</div>
)
}
d)自定义结构
例如用户想通过父组件自定义子组件的结构,可以传递一个render。
子组件先判断是否有render,有则使用render中传入的内容,没有再使用默认的内容
父组件:
<Child
data={list}
render={(item) => {
return <h1>姓名:{item.name};年龄:{item.age}</h1>
}}>
</Child>
子组件:
return (
<li key={item.name}>
{
render ?
render(item) :
<span>
name: {item.name} <br />
age: {item.age}<br />
</span>
}
<Button type="primary" onClick={() => { child_Del(index) }}>删除</Button>
</li>
)
(3)受控组件与非受控组件
在一个受控组件中,表单数据是由 React 组件来管理的。另一种替代方案是使用非受控组件,这时表单数据将交由 DOM 节点来处理。【官方推荐使用受控组件来处理表单数据】
受控组件:受自身控制的组件
<input
type="text"
value={name}
onChange={(e) => { this.onChange(e) }} />
非受控组件:不受当前组件控制
- 使用
React.createRef()定义,this.xxx.current,value获取组件值
class input extends React.Component {
state = {
name: '赵彤'
}
www = React.createRef()
componentDidMount() {
this.www.current.value = this.state.name
}
render() {
return (
<div>
<input type="text" ref={this.www} />
</div>
)
}
}
3. State & 生命周期
(1) props
称为属性,父组件传进来的数据
(2) State
- State 与 props 类似,但 state 是私有的,并且完全受控于当前组件
- 称为状态,组件自己独享的数据
class App extends React.Component {
constructor() {
super(); // es6继承必须用super调用父类的constructor
this.state = { };
}
render() {
return (
<h1>类组件</h1>
)
}
}
不写constructor(props),在render中也可以获取到props (this.props)
(3) 生命周期 & 钩子函数
类组件有钩子函数,函数式组件没有钩子函数
-
挂载(mount):
当组件实例被创建并插入Dom中时,其生命周期调用顺序如下:
-
constructor() -
static getDerivedStateFromProps():「新增」-
会在render之前调用,初始化时和更新后都会调用。
-
不能和 componentWillMount 同时存在。(componentWillMount/UNSAFE_componentWillMount是同一个意思只是版本不同写法不同,目前都不推荐使用了,即将废除。相当于vue中的beforeMount)
-
-
render() -
componentDidMount():「常用」方法会在组件加载完成(被渲染到 DOM 中)后触发,相当于vue中的 mounted 。
ajax请求一般放在
componentDidMount()中 -
-
更新:
当组件的 props 或 state 发生变化时会触发更新。组件更新的生命周期调用顺序如下:
-
static getDerivedStateFromProps()「新增」在render之前执行调用
-
shouldComponentUpdate()「新增」-
这是一个用来优化的钩子函数.根据
shouldComponentUpdate()的返回值,判断 React 组件的输出是否受当前 state 或 props 更改的影响 -
当 props 或 state 发生变化时,
shouldComponentUpdate()会在渲染执行之前被调用。返回值默认为 true
shouldComponentUpdate(nextProps, nextState) -
-
render() -
getSnapshotBeforeUpdate()「新增」「常用」 -
componentDidUpdate()- 相当于vue中的updated
-
-
卸载(unmount):
组件从Dom中被移除时触发。
componentWillUnmount()「常用」 方法清除组件内容,例如清除计时器,相当于vue中的beforeDestroy
-
错误处理
当渲染过程,生命周期,或子组件的构造函数中抛出错误时,会调用如下方法:
-
static getDerivedStateFromError()在后代组件抛出错误后被调用,它将抛出的错误作为参数,并返回一个值以更新 state
-
componentDidCatch()在后代组件抛出错误后被调用
-
-
其他
forceUpdate()强制重新渲染。
(5)setState()
用于类组件
class BusinessInfo extends React.Component {
state = {
count: 100
}
add(num) {
this.setState({
count:this.state.count + num
})
}
minus(num) {
this.setState({
count:this.state.count - num
})
}
render() {
let { count } = this.state
return (<div>
<h1>当前商品数量:{count}</h1>
<Button onClick={() => { this.add(10) }}>+</Button>
<Button onClick={() => { this.minus(5) }}>-</Button>
</div>
)
}
}
-
(1)不要直接修改state,因为代码不会重新渲染组件。应该使用react内部提供的setState()。
this.state.count = this.state.count + numcount数据已经改变了,但是视图不变,因为单项数据流,所以不会重新渲染组件
this.setState({ count:this.state.count + num },function(){ console.log('数据更新完成') })- setState接受第二个参数,当数据更新完成后触发 构造函数是唯一可以给this.state赋值的地方。
-
(2)State的更新大部分情况下是异步的
考虑性能,react可能会将多个setState()调用合并成一个。且this.props和this.state可能会异步更新,所以不能完全依赖它们来更新下一个状态,以下方法可以解决更新问题
写法1: this.setState((state,props) => { counter:state.counter + props.increment }) 写法2: this.setState(function(state,props){ return { counter:state.counter + props.increment } }) -
(3)State的更新会被合并
这里说的合并,是指如果通过setState()方法更新数据,原state中的属性在展示上不会丢失。
「这里区别于函数式组件使用useState,未更新的内容会丢失,详细内容见下文Hook里的userState总结」
(6)单向数据流
任何的 state 总是所属于特定的组件,而且从该 state 派生的任何数据或 UI 只能影响树中“低于”它们的组件。
4. 事件处理
-
react事件命名使用小驼峰,不是纯小写
-
使用JSX语法,要传入一个函数作为事件处理函数,而不是一个字符串。
<button onClick={activateLsasers}> Activate Lasers </button>
-
阻止默认行为,要显式的使用 preventDefault
-
向事件处理程序传递参数
类数组 ,需要保证函数中的this是当前实例
方式1:箭头函数方式 「事件对象必须显式传递」
<button onClick={(e) => this.deleteRow(id, e)}> Delete Row </button> // 此时this,也是render函数中的this,是当前实例方式2:bind方式 「事件对象以及更多参数将会被隐式传递」
<button onClick={this.deleteRow.bind(this,id)}> Delete Row </button> // 此时的this也是当前实例
this
在javaScript中,class的方法默认不会绑定this,如果忘记绑定this.handleClick,并把它传入onClick,当调用这个函数的时候,this的值为undefined
class Toggle extends React.Component {
constructor(props){
super(props)
// 为了在回调中使用 `this`,这个绑定是必不可少的
this.handleClick = this.handleClick.bind(this) // *重点*
}
}
5. 条件渲染
- 元素变量
- 与运算符 &&
- 三目运算符
阻止组件渲染:「根据props中的属性值进行条件渲染」
6. 列表 & key
-
使用map()方法遍历数组,将遍历后的数据放在标签里,如:
<li>标签,再将<li>标签组成的数组插入到<ul>元素中,最后渲染进Dom{ list.map(item => { return <li key={item.name}> {item.name} </li> }) } -
key:给数组中的每一个元素赋予一个确定标识
- 独一无二的字符串,通常使用数据中的id
- 实在没有id等唯一标识,可以使用索引index(前提是静态数据,不会引起列表顺序变化的,否则会引起bug)
- 如果不指定key,react会默认index作为key【同vue】
- 位置:一个好的经验法则是:在
map()方法中的元素需要设置 key 属性。
7. 表单
(1)input
export default class input extends React.Component {
state = {
name: '赵彤'
}
onChange(e){
console.log(e.target.value);
this.setState({
name:e.target.value
})
}
render() {
let { name } = this.state
return (
<div>
<p>{name}</p>
<input
type="text"
value={name}
onChange={(e) => { this.onChange(e) }} />
// 在react中,onChange和onInput效果一样
</div>
)
}
}
8. 状态提升
官方解释:多个组件需要反映相同的变化数据,这时我们建议将共享状态提升到最近的共同父组件中去。
也就是:当前组件私有的state,提升到上层组件(具体父亲还是爷爷组件看情况)统一管理,是一种思想。
三、Hook
React提供的Hooks函数,目的是让”函数式组件“能拥有类似于“类组件”的一些效果
- useState 在函数式组件中的应用状态
- useEffect 拥有生命周期
- useRef 让其使用DOM
- useReducer 让其能够像redux一样管理状态
- ......
Hook 只能用做最顶层作用域,不能写到{}这种块级作用域中,否则会报错。
1. useState
(1)基本使用
使用 useState,主要解决了函数式组件没有自己的状态,及没有钩子函数特性的问题。
普通写法让数据改变,但视图不更新,要更新视图就需要使用hook中的useState
import React, { useState } from 'react';
function BusinessInfo1() {
let [getCount, setCount] = useState({
count: 100,
name: 'A'
})
const add = (num) => {
// count = count + num // 数据变化,视图未更新
setCount({
...getCount,
count:getCount.count + num
})
}
const minus = (num) => {
setCount({
...getCount,
count:getCount.count - num
})
}
return (
<div>
<h1>{getCount.name}当前商品数量:{getCount.count}</h1>
<Button onClick={() => { add(10) }}>+</Button>
<Button onClick={() => { minus(5) }}>-</Button>
</div>
)
}
let [getCount, setCount] = useState({ count: 100, name: 'A' })
- useState 接收的参数是初始值
- 使用useState存在一个问题,它拿到的结果默认不会合并,而是顶替,所以需要手动把其他属性补上
...getCount
(2)异步操作情况
例如在累计计数时加一个定时,定时器到时间输出的还是原来值,并不是最新的值,原因是每次都是一个独立作用域。
function UseState1() {
let [count, setCount] = useState(100)
console.log(`第${count}次`);
const log = () => {
setTimeout(() => {
console.log('log:', count);
}, 2000)
}
return (
<div>
<h1>{count}</h1>
<button onClick={() => { setCount(count + 1) }}>+</button>
<button onClick={log}>log</button>
</div>
)
}
如何拿到最新值呢?可以将新数据存起来。
方法1:原生方法「创建外部作用域」
注意: //*** 为改动点
let outCount = 100 //***
function UseState1() {
let [count, setCount] = useState(100)
console.log(`第${count}次`);
const log = () => {
setTimeout(() => {
console.log('log:', outCount); //***
}, 2000)
}
return (
<div>
<h1>{count}</h1>
<button onClick={() => { setCount(count + 1); outCount = count + 1 }}>+</button> //***
<button onClick={log}>log</button>
</div>
)
}
方法2:react思想「useRef(),原理也是创建外部作用域」
function UseState1() {
let [count, setCount] = useState(100)
const ref = useRef() //***
const log = () => {
setTimeout(() => {
console.log('log:', ref.current); //***
}, 2000)
}
return (
<div>
<h1>{count}</h1>
<button onClick={() => { setCount(count + 1); ref.current = count + 1 }}>+</button> //***
<button onClick={log}>log</button>
</div>
)
}
方法3:回调函数
function UseState1() {
let [count, setCount] = useState(100)
console.log(`第${count}次`);
const log = () => {
setTimeout(() => {
setCount((newVal) => { //***
console.log(newVal);
return newVal + 10
})
}, 2000)
}
return (
<div>
<h1>{count}</h1>
<button onClick={() => { setCount(count + 1) }}>+</button>
<button onClick={log}>log</button>
</div>
)
}
2. useEffect
类组件有钩子函数,函数式组件没有钩子函数,所以就需要用到useEffect
useEffect(() => {
console.log('componentDidMount + componentDidUpdate');
},[])
-
useEffect接收两个参数,如果不写第二个参数,效果相当于
componentDidMount + componentDidUpdate,初始加载会执行,组件更新完也会执行。 -
给第二个参数添加一个空数组,相当于只执行
componentDidMount。(初始加载会执行,组件更新不执行) -
设置依赖性:数组里放的是依赖项,只有依赖项发生改变的时候,才会触发这个钩子
-
useEffect 的回调函数
useEffect(() => { return () => { } })- 更新时会触发,回调函数可以相当于
componentDidUpdate的效果。
- 更新时会触发,回调函数可以相当于
3. useRef
让其可以进行DOM操作.
(1)使用ref
let getText = useRef()
(2)想要获取哪个dom元素,就给它的ref属性赋值
<span ref={getText}>hello</span>
(3)基于current属性可以获取到DOM元素
<button onClick={()=>{
console.log(getText.current.innerHTML); // hello
}}> useRef</button>
4. memo「处理组件」
-
memo处理过的组件,相当于自动执行的,类似于类组件的shouldComponentUpdate
-
只要传进来的数据不发生改变,那么对应的组件就不会重新渲染
Child = memo(Child) -
React.memo类似于PureComponent两者区别:
React.memo只判断props是否改变,改变触发更新,state是否变化不处理 ;PureComponent都会处理。
5. React.PureComponent
-
继承
React.PureComponent,其内部实现了shouleComponentUpdate,只实现了浅层比较(引用数据类型,只比较第一层,内部检测不到)shouleComponentUpdate(nextProps, nextState) 接收2个参数
-
React.Component存在一个问题,父组件更新后,如果没有做任何处理,子组件的render函数都会重新执行一遍。如果某个组件不需要更新,可以使用shouleComponentUpdate钩子函数进行判断
6. useMemo「处理数据」
useMemo是配合memo使用的,它可以缓存对象地址。
它会在某个依赖项改变时重新计算值,这种优化有助于避免在每次渲染时都进行高开销的计算。
let obj = useMemo(() => {
}, [count])
- 参数1:回调函数
- 参数2:依赖 「依赖(count)发生改变,给obj一个新地址;没有改变不会赋值新地址」
- obj:回调结果
memo和useMemos示例代码
function Child(props) {
console.log('render');
return <h1>{props.data.count}</h1>
}
const Childs = React.memo(Child)
function useMemos() {
let [count, setCount] = useState(100)
let [age, setAge] = useState(10)
let obj = useMemo(() => {
return {
count: count
}
}, [count])
return (
<div>
<Childs data={obj} />
<button onClick={() => { setCount(count + 3) }}>count + </button>
<h2>{age}</h2>
<button onClick={() => { setAge(age + 1) }}>age + </button>
</div>
)
}
7. useCallback
React.memo, useMemo, useCallback 三个是一个组合,主要用于优化.
useCallback用于缓存函数地址
let f = () => {
setCount(count + 10)
}
f = useCallback(f, [])
return (
<div>
<Childs data={obj} onChangeCount={f} />
<button onClick={() => { setCount(count + 3) }} >count + </button>
<h2>{age}</h2>
<button onClick={() => { setAge(age + 1) }}>age + </button>
</div>
)
8. useReducer
useReducer 是useState的一个替代方案
四、路由
1. 安装
在浏览器中使用
npm install react-router-dom
2. 基础组件
(1) 路由组件(router components)
<BrowserRouter>:浏览器模式<HashRouter>:hash模式 使用路由组件,要确保在根组件上使用。
(2) 路由匹配组件(route matchers components)
<Route><Switch>在<Switch>里可以包裹多个<Route>,浏览器地址会依次比较path地址,匹配则显示,不匹配返回null。
<Switch>
<Redirect path='/' exact to='/introduce'></Redirect>
<Route path='/introduce' component={Introduce}></Route>
<Route path="/Users" component={UserInfo}></Route>
</Switch>
exact
当前路由path的路径采用精确匹配
(3) 导航组件(navigation components)
-
<Link>: 会被渲染为一个<a>标签<Link to="/">Home</Link> -
<NavLink> -
<Redirect>:强制跳转某页面可以使用这个标签,例如是否是登录状态。
引入依赖
import {
BrowserRouter as Router,
Switch,
Route,
Link
} from "react-router-dom";
3. 其他属性
lazy懒加载
import React, { lazy } from 'react'
const login = lazy(() => import('@/views/Login'))
Suspense
延迟加载