React环境安装
- 安装全局脚手架
npm i create-react-app -g - 创建项目
reate-react-app my-app 创建项目,应用名称不要有大写字母 - npm run star 启动项目
- npm run build 打包
JSX语法-JavaScript XML
即在标签中写入JS代码块
语法规则
可以在JS代码块中写入标签,同时可以在标签中插入变量,通过{}插入变量的值
遇到以<开头的代码,以标签的语法解析,与html标签同名的标签转换为html同名元素,其他标签需要特别解析例如:
let App = <div> hello world! </div> //App变量保存了一个标签,可以直接使用
render(App); //直接在页面中渲染一个div标签
import App from './App.jsx' //导入一个类或者函数
render(<App>) //会去解析判断是标签还是类或者函数 与Vue中使用组件相似,会直接渲染其返回的render()
使用JSX
在JSX中插入值
let a = "hello world!";
let App = <div>{a}</div>;
render(App); //页面中会显示出hello world 与Vue中{{}}插入变量相同,但React中{}不是响应式的需要自己添加到状态中
单独的JSX文件
在React中可以引入JSX文件,其导出一个类或者函数
创建一个App的JSX文件,App.jsx文件中,通过render()返回需要渲染的内容
import React, { Component } from 'react'
export default class App extends Component {
render() {
return (
<div>
</div>
)
}
}
在index.js文件中引入JSX文件,与Vue引入组件相同
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.jsx' //引入JSX文件
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<App/> //页面渲染App组件,为了方便理解可以理解为:<App/> ==> new App() ,但实际不是在创建对象
);
样式
React推荐使用内联样式。React 会在指定元素数字后自动添加 px。
var myStyle = {
fontSize: 100, //等价于fontSize: '100px',
color: '#00ff00'
};
render(
<h1 style = {myStyle}>我是标题</h1>
);
注释
注释需要写在花括号中,需要注意的是:
- 在标签内部的注释需要花括号
- 在标签外的的注释不能使用花括号
render(
/*标签外部的注释 */
<h1>我是标题 {/*标签内部的注释*/}</h1>
);
JavaScript表达式
在JSX中,如果存在花括号{}则这代表里面的内容是JS环境,用来插入一些JS变量的值
var a=20
function fn(){return "hello"}
render(
<div>
<h1>{a+1}</h1>
<h3>{fn()}</h3>
</div>
);
条件渲染
在JSX文件中不能使用if else语句,但可以使用三目运算符表达式来代替,在JS语法中依然可以使用if else,不能JSX中使用
let i = 10;
<h1>{i>=10?'true':'false'}</h1> //这里可以写标签true或者false返回的表达式可以是标签<div>true</div>,这里就可以用于Vue中的插槽位置,规定那些内容是否显示
数组
React中在模板中插入数组会自动将该数组for循环出来,会自动的讲每一个元素渲染出来
表单与事件
表单组件
受控组件
与Vue的响应式组件相同,即为响应式组件
非受控组件
与Vue的响应式组件相同,即为非响应式组件
事件
事件类型
使用React处理事件在元素上与DOM相似,在语法上存在一点差异
React事件使用小驼峰命名法
React事件使用JSX传递一个函数作为事件处理程序
鼠标事件:onClick onDoubleClick onMouseDown
触摸事件:onTouchStart onTouchMove onTouchEnd
键盘事件:onKeyDown
剪切事件:onCopy onCut onPaste
表单事件:onChange onInput onSubmit
焦点事件:onFocus
UI事件:onScroll
滚动事件:onWheel
事件对象
在React中如果需要阻止默认事件,只能通过事件对象调用**event.preventDefault()**阻止
事件绑定
事件处理函数在使用this时,需要注意其指向
import React, { Component } from 'react'
export default class App extends Component {
constructor() {
super();
this.fn = function () {
console.log(this); //undefined
}.bind(this) //=>可以在这里直接调用bind改变this
}
fn1(){
console.log(this);//undefined
} // =>方法不能通过在这里调用bind需要在下面调用时绑定bind改变this
fn2 =function(){
console.log(this);//undefined
}.bind(this) //=>可以在这里直接调用bind改变this
// 函数定义式即可在后面调用bind(),方法不可以在后面调用bind需要在该函数调用时后面调用bind()
render() {
return (
<div>
<button onClick={this.fn1.bind(this)}>btn1</button>
</div>
)
}
}
避免this指向问题
constructor 内部对事件处理函数bind 绑定this(官方推荐)
事件绑定时都对事件处理函数做bind绑定
事件处理函数都是用箭头函数
事件函数传参
通过绑定call传参,既绑定this又传参,bind不可以传入参数
fn1(e){
console.log(this,e);//APP{},100
}
<button onClick={(e)=>this.fn1.call(this,100)}>btn2</button>
// 箭头函数中的e表示的是事件event,如果需要传入事件则需要自己手动传入e
<button onClick={this.fn1}>btn2</button> //默认将event事件传入,如果需要使用则自己用一个参数接收使用即可
组件生命周期
当组件实例被创建并将其插入 DOM 时,将按以下顺序调用这些方法:
constructor() //构造函数
static getDerivedStateFromProps() //获取传入的属性值和状态
render() //渲染
componentDidMount() //组件挂载完成
更新可以由对 props 或 state 的更改引起。当重新渲染组件时,按以下顺序调用这些方法:
static getDerivedStateFromProps() //获取传入的属性值和状态
shouldComponentUpdate() //是否需要重新渲染
render() //渲染
getSnapshotBeforeUpdate() //组件更新前的快照,常用于下拉框位置
componentDidUpdate() //组件更新完毕
-
**组件创建阶段:**一辈子只执行一次
componentWillMount:
render:
componentDidMount: -
**组件运行阶段:**按需,根据props 属性或state 状态的改变,有选择性的执行0 到多次
componentWillReceiveProps:
shouldComponentUpdate:
componentWillUpdate:
render:
componentDidUpdate: -
**组件销毁阶段:**一辈子只执行一次
生命钩子解析
- componentWillMount :在渲染前调用,在客户端也在服务端。
- componentDidMount : 在第一次渲染后调用,只在客户端。之后组件已经生成了对应的DOM结构,可以通过this.getDOMNode()来进行访问。 如果你想和其他JavaScript框架一起使用,可以在这个方法中调用setTimeout, setInterval或者发送AJAX请求等操作(防止异部操作阻塞UI)。
- componentWillReceiveProps :在组件接收到一个新的 prop (更新后)时被调用。这个方法在初始化render时不会被调用。
- shouldComponentUpdate :返回一个布尔值。在组件接收到新的props或者state时被调用。在初始化时或者使用forceUpdate时不被调用。 可以在你确认不需要更新组件时使用。
- componentWillUpdate:在组件接收到新的props或者state但还没有render时被调用。在初始化时不会被调用。
- componentDidUpdate :在组件完成更新后立即调用。在初始化时不会被调用。
- componentWillUnmount: 在组件从 DOM 中移除的时候立刻被调用。
可以将父组件的状态传给子组件
路由 ——版本6.0以上
下载
npm i react-router-dom -S
BrowserRouter,HashRouter
HashRouter:Hash路由
BrowserRouter:history路由
创建路由
import {BrowserRouter,Route,Routes} from 'react-router-dom'
<BrowserRouter>
<Routes>
<Route path='/main' element={<Main />} />
<Route path='/one' element={<One />} />
<Route path='/two' element={<Two />} />
</Routes>
</BrowserRouter>
基本使用
import React, { Component } from 'react';
import {BrowserRouter,Route,Routes} from 'react-router-dom'
export default class routers extends Component {
render() {
return (
<div>
// Route 可以嵌套子路由
<BrowserRouter>
<Routes>
<Route path="/home" element={<Home></Home>}>
<Route path="/home/a" element={<Son1></Son1>}></Route>
</Route>
<Route path="/pwd" element={<Son1></Son1>}></Route>
<Route path="/*" element={<Home></Home>}></Route> //通配路由
</Routes>
</BrowserRouter>
</div>
)
}
}
页面跳转 Link和useNavigate
Link
Link标签相当于是超链接,属性to可以是字符串也可以是对象
import {Link, useNavigate} from 'react-router-dom';
<Link to={{pathname:'/one'}}>跳转到one</Link>
<Link to='/one'>跳转到one</Link>
存在属性state可以通过该属性传递参数,传递的内容不会显示在网页中,也可以直接在链接中拼接内容
import {Link, useNavigate} from 'react-router-dom';
<Link to={{pathname:'/one'}}>跳转到one</Link> //如果是对象则不能直接拼接到链接中,可以通过state传递内容
<Link to='/one?uid=20' state={{uid:123}}>跳转到one</Link> //可以直接拼接在链接中也可以通过state传递内容
//接收state传递的内容通过useLocation()的返回值调用state属性接收
//import {useLocation} from 'react-router-dom';
//const location = useLocation();
//obj = location.state;
useNavigate
useNavigate是一个hook常用在事件跳转中
import {useNavigate} from 'react-router-dom';
let navigate = useNavigate();
navigate({pathname:'/one'},{state:{uid:1,name:"Tom",age:"20"}});//第一个参数传路径,第二用来传递内容
动态路由传参数
动态路由传参在设置路由时需要设置可传参数,通过useParams函数接收参数
设置可传参数
<Route path='/home/:name/:sex' element={<Son1 />} />
:就代表着可传参数
页面跳转时,路径必须要设置的路由相匹配
navigate({pathname:`/home/Tom/18`});
<Link to={{pathname:"/home/Tom/18"}}>home</Link>
接收参数使用hook:useParams
import {useParams} from 'react-router-dom';
let obj = useParams();
路由嵌套(子路由)
路由嵌套需要在Route标签中嵌套Route,子路由的path=":path"
**需要在父组件的内部留一个Outlet标签用来显示子路由**
//父路由:
import {Outlet} from "react-router-dom"
render(<div>
<Outlet></Outlet> //子组件显示的位置
</div>)
重定向
路由重定向需要使用Navigate组件,在设置路由时,通过该组件重定向到其他页面
import {Navigate} from 'react-router-dom';
import Home from './Home.jsx'
<Route path="/test" element={<Navigate to="/home" />} /> //重定向到首页
<Route path="/*" element={Home/>} /> //通配符直接返回首页
可以在组件中传数据,通过props接收数据用来鉴权这些
redux
安装:npm i redux
核心API
createStore(reducer):创建数据仓库: import {createStore} from “redux”;
store.getState():用于获取store里面的数据;
store.dispatch(action):用于派发action,触发reducer,执行修改动作;action只能是一个对象
store.subscribe(componentMethods):订阅store的修改,只要store发生改变,组件中的回调函数就会被执行;
使用
import {createStore} from 'redux'
// 创建默认数据源
const defaultState = {
username:"张三",
list:['a','b','c']
}
// 创建数据仓库
const store = createStore((state=defaultState,action)=>{
//action修改数据后进行操作
// 传入的action对象中必须有一个type属性,action存在一个初始type,当通过dispatch传入后需要自己携带一个type
if(action.type =='App'){
// 修改username
state.username = action.username
}
return state
});
export default store;
//修改仓库数据中的数据
store.dispatch({ type: "App", username: "李四" }) //派发action执行修改
//订阅响应更新数据
componentDidMount() {
store.subscribe(() => { //订阅响应更新数据
this.setState(store.getState())
})
}
change() {
store.dispatch({ type: "App", username: "李四" })//派发action执行修改
}
Hook
过渡技术
无用渲染
shouldComponentUpdate
当某一些状态(数据)发生改变时,并不希望去重新渲染页面,此时可以通过shouldComponentUpdate()去判断是否需要渲染
import React, { Component } from 'react'
export default class RendeLess extends Component {
state = {
count: 10
};
shouldComponentUpdate() {
if(this.state.count>=20){
return false
}
return true
}
change() {
this.state.count++;
this.setState(this.state)
}
render() {
return (
<div>
<p>{this.state.count}</p>
<button onClick={this.change.bind(this)}>changge</button>
</div>
)
}
}
PureComponent
会将组件现在的state和props和其下一个state和props进行浅比较,如果它们的值没有变化,就不会进行更新
如果我们使用PureComponent时就需要使用全覆盖(即通过setState({}))来重新刷新页面,由于是浅拷贝可能导致setState(this.state)不会重新刷新页面,至少我这么使用的时候没有刷新
import React, { PureComponent } from 'react'
export default class PureLess extends PureComponent {
state = {
num: 10
};
change() {
this.state.num++;
// this.setState(this.state); //这里并没有重新刷新页面
this.setState({ num: this.state.num++ }) //使用全覆盖才会刷新页面
}
render() {
return (
<div>
<p>Pure-----{this.state.num}</p>
<button onClick={this.change.bind(this)}>Purechange2</button>
</div>
)
}
}
React.memo
该方法只是用来作为性能优化而存在的,如果依赖它来"渲染",会产生bug
它的作用和React.PureComponent类似,是用来控制函数组件的重新渲染的。React.memo(...)其实就是函数组件的React.PureComponent
仅检查props变更,如果函数组件被 React.memo 包裹,且其实现中拥有useState,useReducer或useContext的Hook,当state或context发生变化时,它仍会重新渲染。
使用:导出经过memo()处理的函数组件
// App.jsx
import React, { Component } from 'react'
import FuncLess from './FuncLess.jsx'
export default class App extends Component {
state = {
count: 20
}
changeCount() {
// this.state.count = 200; //memo的子组件函数只执行一次
this.state.count++; //memo的子组件函数会跟着执行
this.setState(this.state);
}
render() {
return (
<div>
<FuncLess count={this.state.count}></FuncLess>
<button onClick={this.changeCount.bind(this)}>change-count</button>
</div>
)
}
}
// FuncLess.jsx
import React, { memo } from 'react'
function FuncLess(props) {
console.log(props.count);//根据props改变的值是否相同,相同则不执行,不相同则执行
return (
<div>
<p> FuncLess-{props.count}</p>
</div>
)
}
export default memo(FuncLess)
context:上下文
props,state传递数据,数据自顶下流,使用context实现父传孙,跨组件传数据
如果要Context发挥作用,需要用到两种组件,一个是Context生产者(Provider),通常是一个父节点,另外是一个Context的消费者,通常是一个或者多个子节点。所以Context的使用基于生产者消费者模式。
使用Context
创建一个context对象,提供者需要使用ctx对象标签 context.Provider,消费者需要使用标签context.Consumer,同时在导出消费者组件时需要在前面添加属性.contextType = context,否则会报错
//ctx.jsx context.jsx
import {createContext} from 'react'
let ctx = createContext()
export default ctx
//提供者父组件中使用
//App.jsx
import React, { Component } from 'react'
import ctx from './ctx.jsx'
import Box from './Box.jsx'
export default class App extends Component {
render() {
return (
<div>
<ctx.Provider value={{msg:"hello",name:"Tom",age:"20"}}>
<Box></Box>
</ctx.Provider>
</div>
)
}
}
//消费者 Inject.jsx
import React, { Component } from 'react'
import ctx from './ctx.jsx'
class Inject extends Component {
render() {
return (
<ctx.Consumer>
{(data) => { return (<h1>{data.msg}--{data.name}--{data.age}</h1>) }}
</ctx.Consumer>
)
}
}
Inject.contextType = ctx
export default Inject;
也可以直接给子组件使用Context传值用法相同
useState
useState与类组件中的this.state相同,都是用来管理组件状态的。在React Hook没出来之前,函数组件也叫做Functional Stateless Component(FSC:功能无状态的组件)
useState会返回一个数组,数组中有两个元素[initialState,callBack],返回的数组中第一个表示当前的state的最新值,第二个用来更新state的函数
常用数组解构useState()方便使用
// UseState.jsx
import React, { useState } from 'react'
export default function UseState() {
let [state, setState] = useState('hello');//数组解构赋值,初始化的state是传入的参数
return (
<div>
<p>UseState-{state}</p>
<button onClick={() => setState(state = 666)}>changeState</button>
</div>
)
}
与类组件中的this.state不同的是,**函数组件的setState是全量替代(覆盖)**类组件中设置的state是浅归并到state
useEffect
使用
useEffect(()=>{return clear()},[deps]) //传入一个函数,和依赖项只有依赖性发生改变才会执行函数,没有依赖项时页面重新渲染就会执行该函数
// 传入的函数中返回值时返回值函数
// 依赖性用来限制该副作用的执行条件
import React, { useState, useEffect } from 'react'
export default function UseEffect() {
let [count, setCount] = useState(20);
let changeCount = () => {
setCount(count++)
}
let [num, setNume] = useState(0);
useEffect(() => {
console.log(666);
let timer = setTimeout(() => {
console.log("this is useEffect");
}, 2000); //如果没有return清楚则每当页面状态发生改变就会一直执行
return (() => {
clearTimeout(timer)
})
}, [num]) //第二个参数表示依赖,依赖的状态发生改变则该useEffect就会运行
let changeNum = () => {
setNume(num++);
}
return (
<div>
<p>UseEffect-{count}</p>
<button onClick={changeCount}>changeCount</button>
<p>{num}</p>
<button onClick={changeNum}>changeNum</button>
</div>
)
}
useCallback
在函数组件内部写内嵌函数时,每当状态(数据)发生改变时,重新渲染时,会重新生成这个函数,如果作为props传递给子组件,即使props没有发生改变,都会使子组件重新渲染,这种无用的渲染可能会产生一些性能问题
使用
useCallback(callback,[deps]) //传入一个函数,和依赖项,只有当依赖项中的数据变化了才会去重新执行函数
// 传入的callback会被记住,只有依赖项改变时才会去重新执行返回新的定义函数,否则useCallback都会返回之前定义的函数。
可以通过useEffect的依赖项查看该内置函数是否有重新渲染
import React,{useState,useEffect,useCallback} from 'react'
export default function CallBack() {
let [num,setNum] = useState(2)
let changeNum = ()=>{
setNum(num++)
}
useEffect(()=>{
console.log("changeNum生成了");//状态发生改变重新渲染内置函数就重新生成了
})
return (
<div>
<p>CallBack-{num}</p>
<button onClick={changeNum}>changeNum</button>
</div>
)
}
因此官网提供了useCallback来记住当前定义的函数并在下次组件渲染的时候返回之前定义的函数而不是使用新定义的函数。
useCallback就是为了用来解决这种性能问题的hook
import React, { useState, useEffect, useCallback } from 'react'
export default function CallBack() {
let [num, setNum] = useState(2)
let changeNum = useCallback(() => {
setNum(num++)
}, []);
useEffect(() => {
console.log("changeNum生成了");//只运行了一次
}, [changeNum])
return (
<div>
<p>CallBack-{num}</p>
<button onClick={changeNum}>changeNum</button>
</div>
)
}
useMemo
useMemo和useCallback的作用十分类似,只不过它允许你记住任何类型的变量(不只是函数),useCallback只允许记住函数
使用
useMemo(()=>{},[deps]) //传入一个函数,和依赖项,只有当依赖项中的数据变化了才会去重新执行函数
// 会将函数的返回值记住,只有依赖项改变才会去重新执行函数。与vue计算属性相似,不过react需要自己去设定监听的属性
举例
import React, { useMemo } from 'react'
export default function UseMemo(props) {
let Age = () => {
console.log("重新渲染就会执行一次");//只要age修改就会重新传值,重新计算
if (props[0].age >= 18) {
return "成年了"
} else {
return "未成年"
}
};
let test = () => {
console.log("只有传入的name改变才会执行");
return props[0].name
}
let name = useMemo(() => test(), [props[0].name]) //你重新渲染,只有name修改后才会去调用test重新计算,否则就去使用上次函数计算的结果
return (
<div>
<p>{props[0].name}-{props[0].age}</p>
<p>{Age()}-{name}</p>
</div>
)
}
useContext
const value = useContext(MyContext);//MyContext 表示的是上下文对象
接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值
通过调用 Context.Provider 标签中的value属性将需要传值的数据传下去,子代通过useContext("上下文")获取传入的值即可直接使用
使用
//先创建一个数据仓库
import React from "react";
let ctx = React.createContext(null);
export default ctx //导出这个数据仓库对象
//父级元素引入数据仓库对象
//通过其Provider属性标签包括获取数据的标签
//value传入数据
import ctx from './ctx';
import Box from './Box.jsx'
<ctx.Provider value={{ name: 'Tom', age: "20" }}>
<Box></Box>
</ctx.Provider>
//子组件通过引入数据仓库对象
import React, { useContext } from 'react'
import ctx from './ctx'
export default function Box() {
let obj = useContext(ctx); //获取父组件传入进来的数据
return (
<div>Box-{obj.name}</div>
)
}
自定义Hook
Hook使用场景:
use开头的那些官方提供的Hook函数只能在函数组件内部或者自定义Hook函数中使用,不能在类或者普通事件函数中使用
自定义Hook:
利用官方提供的Hook来实现自己的一个具有业务功能的函数,特点是使用后会返回一个组件,这个思想就是类组件中高阶组件的思想
通过自定义 Hook,可以将组件逻辑提取到可重用的函数中。
useReducer
const [state, dispatch] = useReducer(reducer, initialArg, init);
官方说法:useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法
使用
useReducer(reducer:function,state:any,func:function) //返回一个数组["数据","函数"]
reducer:(state,action)=>{
//通过对dispatch函数传入进来的action对象进行处理
state = JSON.parse(JSON.stringify(state)); //深拷贝:保证唯一数据源
return state
}
state:默认的初始数据
func:会将state数据传入进去,通过处理后返回初始数据
import React, { useReducer } from 'react'
export default function UseReducer() {
let [state, dispatch] = useReducer((state, action) => {
if (action.type == 'MSG') {
state.msg = action.value
}
state = JSON.parse(JSON.stringify(state)); //需要保证传出的数据只能是可读的并且保证是基于原来对象修改后的一个全新的对象
// 保证唯一数据源,因此返回的必须是一个全新的对象
return state
}, { msg: 'Tom' }, (arg) => {
arg.msg = 666
return arg
});
return (
<div>
<p>UseReducer-{state.msg}</p>
<button onClick={() => { dispatch({ type: 'MSG', value: "msg被修改了" }) }}>change</button>
</div>
)
}