一、为什么用react
1、开发团队是fackbook团队,实力强大,开源
2、社区完善,很多问题都能找到
3、很多国内外大型互联网公司都在使用
4、优秀的diff算法、高效,使用虚拟dom,不直接操作页面
二、虚拟dom
DOM:浏览器本质,以js对象的方式表示页面元素,并提供api
按需更新就是用到了虚拟dom,通过比较新旧两颗DOM结构,按需更新
虚拟DOM:用JS对象模拟页面上的DOM和DOM嵌套关系,使DOM高效更新
var div = {
tagname:'div'
attr:{
id:'test'
}
}
三、diff算法
diff:新旧两颗DOM树对比时,需要逐层比较,这个过程就是diff算法;
数据改变的时候,react不会直接到去修改dom,而是生成新的虚拟dom,通过对比旧的虚拟dom,有改变的标签或节点就会生成对应部分的真实dom,没改变的部分则直接使用旧的真实dom
为什么不建议使用index作为key值?
key作为虚拟dom的唯一标识,如果有逆向增加、逆向删除等操作,key值就会变乱,导致dom全部重新生成,页面渲染变慢甚至出现数据混乱
webpack搭建项目并启动:
npm init -y 初始化
npm i webpack webpack-cli -D //安装webpack
npm i webpack-dev-server -D //安装自动运行,将打包后的main.js放在根目录
npm i html-webpack-plugin -D //将index.html放到根目录内存
pack.json
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"start": "webpack serve --open chrome.exe"
},
webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')//导入自动自动生成index.html页面到根目录
const htmlPlugin = new HtmlWebpackPlugin({
template:path.join(__dirname,'./src/index.html'),//源文件路劲,__dirname是当前目录
filename:'index.html' //内存页生成的文件名称
})
module.exports = {
mode:'production',
plugins:[
htmlPlugin
]
}
webpack结合react使用jsx过程和所需安装的包
babel升级,踩坑!Error: Cannot find module '@babel/core'_兔子零-CSDN博客
JSX语法(创建虚拟dom的最简单最直接方式)
const a = 1000
const jsxDom = <p>你好</p>
const title = 'testval'
ReactDom.render(<div
title={title}>
{a}{jsxDom}
{{arr.map(item=><h3 key={item}>{{item}}</h3>)}} //循环需要加key
</div>,
document.getElementById('app')
)
脚手架安装
npm i -g create-react-app //只需执行一次,二次创建则不用再执行这命令
create-react-app xxx //项目名不用大写
VSCODE安装以下插件:
ES7+ React/Redux/React-Native snippets
用快捷键:rcc 快速生成react模板
创建组件和传值(父传子):
const dog= {
name:'小狗'
age:3
}
function Hello (props) { //方法名开头一定要大写
return <div>Hello组件,传值{props.name},{props.age}</div>
}
ReactDom.render(<div>
<Hello name={dog.name} age={dog.age}></Hello>
//或使用扩展
<Hello {...dog}></Hello>
</div>)
法二、
新建Hello.jsx文件:
import React from 'react' //组件文件要引入,大写R开头
export default function Hello (props) { //方法名开头一定要大写
return <div>Hello组件,传值{props.name},{props.age}</div>
}
引入:
import Hello from 'xxx/xxx/Hello.jsx'
法三、使用class创建
class ClassName extends React.Component{
render(){
return <div>{this.props.name}</div> //直接使用this.props拿到传值
}
}
子传父
状态在哪,就在哪操作
父把操作的方法传给子,子通过prop调用这个方法,把新数据通过参数的形式返回给父来操作。
父:
const getChildToChange = ()=>{//todo}
<child getChildToChange={this.getChildToChange}>
子:
const { getChildToChange } = this.prop
getChildToChange(childdata)
祖孙则再传一层
消息的发布和订阅(兄弟组件传值)
?/安装
npm i pubsub-js --save
//引入
import PubSub from 'pubsub-js'
//A组件发布
PubSub.public('ecentName',dataArr)
B组件接收
this.sub = PubSub.subscribe('ecentName',(msg,data)=>{ data//就是传过来的值 })
卸载发布
PubSub.unsubscribe(this.sub)
prop的类型限制
//npm i props-types
import PropTypes from 'props-types'
class Child extends React.Component{
static PropTypes = {
value1:propsTypes.array.isRequired,//限制传数组
valueName2:propsTypes.func.isRequired //限制传函数
}
}
class创建的组件可以有私有变量属性,可读可改
class ClassName extends React.Component{
constructor(){//构造函数里面的this是指向ClassName实例
super() //子类自定义构造器,必须要调用super
this.state = { //相当于vue中data定义值
test:'这是值'
}
this.test = this.test.bind(this)//将原型上的test方法改变this指向后,生成test挂在实
例上,使test方法里的this指向ClassName实例
}
render(){//react会默认new生成实例调用render方法
return <div>
{this.props.name}--{this.state.test}
<div onClick = {this.test}>点击</div>
//<div onClick = {()=>this.test()}>点击</div> //上面不写bind时用
</div> //直接使用this.props拿到传值
}
test(){//类中的方法都是挂在原型对象上,实例调用时里面的this才是指向ClassName实例否undefind
this.setState({ //修改state定义的值,此方法是异步,需要在回掉中拿到最新值
test:'newval'
},function(){
console.log(this.state.test)
})
}
//此test()是原始的定义方式,this是指向谁调用,如果用在onClick,则this指向window
//此定义的正确使用方式是 onClick="{this.test.bind(this)}"
}
ClassName.propTypes = {
name:PropTypes.string.isRequired,//限制为字符串和必须传
age:PropTypes.number,
tools:PropTypes.func,//传的是方法,类型则是func
}
ClassName.defaultProps = {//设置默认值
age:18,
}
class创建的组件简化写法:
在类里面直接通过key=value会吧key挂在实例上,而不是原型上
原理:剪头函数里的this是指向定义时的this和谁调用无关
class ClassName extends React.Component{
state = {//初始化状态
name:'xxz',
age:18
}
render(){//取值渲染
const { age } = this.state
return <div onClick={this.test} >年龄:{age}</div>
}
test = ()=>{//自定义方法
this.setState({age:20})
}
}
JSX使用style和class
style:
render(<div style={{fontSize:'12px'}}></div>)
class:
新建styleObj.scss文件:
.title{
colot:red
}
使用的js文件引入:
import styleObj from '../../styleObj.scss' //模块化
render(<div className={styleObj.title}></div>)
或
import '../../styleObj.scss' //模块化
render(<div className='title'></div>)
ref
方式一、字符串式(不建议用,影响效率)
<div ref='mydef'></div>
实例:this.refs.mydef
方式二、回调函式(内联式在数据更新时执行两次)
<div ref={(c)=>this.myref=c}></div> //直接创一个变量myref挂载在实例上,c就是当前节点dom
使用:const { myref } = this
方式三、使用React.createRef(推荐)
<div ref={this.myref}></div>
class里面:
myref = React.createRef() //多个ref则名称要不一样
console.log(this.myref.current) //拿到dom
可控和不可控组件
不可控:通过ref取值的不可控
可控:通过函数,将值挂载到state
高阶函数和函数柯里化
高阶函数:A函数接收到参数是一个函数或A函数返回值是一个函数,那A函数就是高阶函数
如:Promise、setTimeout、arr.map(()=>{})等等
函数柯里化:函数会不停的返回函数,并且通过一个函数接收一个参数来处理
原生事件想传额外的值
//高阶函数实现
<input type='checkbox' onChange={this.checkChange(id)}>
const checkChange = (id)=>{
return (e)=>{
console.log(id,e.target.checked)
}
}
//普通函数实现
<input type='checkbox' onChange={()=>{this.checkChange(id)}}>
const checkChange = (id)=>{
console.log(id)
}
生命周期
一、初始化阶段ReactDOM.render()
constructor()
componentWillMount() //后期要加UNFATE_componentWillMount()
render()
componentDidMount()
二、更新阶段
componentWillReceiveProps() //props改变 /后期要加UNFATE_
shouldComponentUpdate()
componentWillUpdate() /后期要加UNFATE_
render()
componentDidUpdate()
三、卸载ReactDOM.unmontComponentAtNode()
componentWillUnmount()
新生命周期
//渲染阶段
constructor()
getDerivedStateFromProps() //需要返回state对象或null
render()
componentDidMount()
//更新阶段
getDerivedStateFromProps()
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate(preProp,preState) //返回任意值除了undefined,componentDidUpdate接收
componentDidUpdate(preProp,preState,snapshot)
//卸载
componentWillUnmount()
样式模块化,防止冲突,相当于vue中的scoped
创建时css文件名:xxx.module.css
使用:
import democss from './xxx.module.css'
className = {democss.xxx}
内联样式
<div style={{color:'red',fontSize:this.state.sixe+'px'}}>
react的 vscode插件
快捷创建组件插件:ES7 React/Redux/GraphQL/React-Native snippets
输入rcc或rfc
配置代理
代理:相当于本地开一个服务器,前端向本地服务器发起请求,本地服务器再向远程服务器转发,
本地服务器和远程服务器不存在跨域
法一:在pack.json文件里
"proxy":"http://xxxx:5000"
法二:配置多个代理
1、在src下新建setupProxy.js
2、
const proxy = require('http-proxy-middleware')//react默认已安装
module.exports = function(app){
app.use(
proxy('/api',{//带有api的都或触发此代理
target:'http://localhost:5000',
changeOrigin:true,//将请求头的host改成和远程服务器的一样
pathRewrite:{'^/api':''}
}
)
)
}
连续性结构赋值
如:
const this= {
item:{
name:'test'
}
}
const {item:{name}} = this
console.log(name)//test
//改字段名
const {item:{name:newname}} = this
console.log(newname)//test
路由:
//安装:npm i react-router-dom
//ipmport {Router,Switch,Route,NavLink} from 'react-router-dom'
//点击跳转到对应路由
<NavLink to="/home"></NavLink>
//注册路由
<Switch> //switch 后面的覆盖前面的
<Route path="/home" component={Home}></Route>
<Redirect to="/home">
</Switch>
app.js下使用:
import { BrowserRouter as Router, Routes, Route, Link } from "react-router-dom";
import Home from '../page/Home.js'
import User from '../page/User.js'
return(
<Router>
<link to='/home'>去首页</link> //菜单
<link to='/user'>去首页</link>
<Routes> //路由注册
<Route path="/" exact element={<Home />}></Route>//exact精确匹配路由,默认显示/home
<Route path="/home" element={<Home />}></Route>
</Routers>
</Router>
)
路由传参数和编程式跳转
params方式:
<link to=`{/home/${id}}`>
<Route path="/home/:id" component={Home}></Route>
//子组建抛出 export default withRouter(home)
this.props.match.params.id
serch方式(query):
import qs from 'querystring'
<link to=`{/home/?id=01&age=18}`>
<Route path="/home" component={Home}></Route>
const serch = this.props.location //?id=01&age=18 (urlencoded字符串)
const {id} = qs.parse(serch.slice(1))
state方式(url上不会出现参数,刷新也不会丢失)
<link to={pathname:"/home",state:{id:1}}>
<Route path="/home" component={Home}></Route>
this.props.location.state
编程式跳转:
this.props.history.push('/home') //replace,goForward,go,goBack
一般组件的路由跳转
//让一般都组件也带有路由组件的props
import {widthrouter} from 'react-router-dom'
class className extends Component{ xx }
export default widthrouter(className)
BrowserRouter和HashRouter区别
BrowserRouter:使用h5的history原理,ie不兼容,路径上没#,刷新有保留props的state
HashRouter:使用锚点原理,全兼容、刷新不保留props的state
react-redux全局状态管理
步奏一:创建store.js
根目录创建store.js:
//安装 : npm i --save redux react-redux
//引入模块
import { legacy_createStore as createStore } from "redux";
//创建状态值,相当于vuex中的state
const initState = {
num:1
}
//创建reducer,是一个函数,固定接收两个参数,参数1是state,参数2是action
//action的值固定格式是{type:xxx,value_xxx:xxx}
const reducer= (state=initState,action)=>{
switch(action.type){
case 'addNum':
return {
...sate,
num:action.num
}
default
return state
}
}
export const store = createStore(reducer);
步奏二:把redux嵌入到react项目中
//在index.js目录中
import { store } from "./store/index";
import {Provider} from 'react-redux'
root.render(
<React.StrictMode>
<Provider store = {store}>
<App />
</Provider>
</React.StrictMode>
);
步奏三:组件中使用store和修改store的值
//在需要用到store的组件中使用rcredux快捷键创建组件
import React, { Component } from 'react'
import { connect } from 'react-redux'
export class myUseCom extends Component {
render() {
const {num,addNum} = this.props //从props中获取到store中的num,和改变方法
return (
<div>
<button onClick={()=>addNum(num+1)}> {num} </button>
</div>
)
}
}
//相当于vuex中的state
const mapStateToProps = (state) => ({
num:state.num //会把num创建到prop中,组件则可以从this.prop获取到该值
})
//相当于vuex中的mutation
const mapDispatchToProps = dispatch => {
addNum:(num)=>dispatch({type:'addNum',num:num})
}
export default connect(mapStateToProps, mapDispatchToProps)(myUseCom)
hook
//hook主张用函数式创建组件,但函数式的this是undefind,state和生命周期如何调用呢
//函数组件中使用useSate和useEffect,就可以模仿state和生命周期的特性
import React,{useSate,useEffect} from 'react'
function demo (){
//useSate hook 模仿state和setState
const [number,setNumber] = useSate(0) //定义初始值0,和修改值得方法
function show(){
setNumber(100)//重置值
}
//useEffect hook 模仿生命周期
useEffect(()=>{
console.log('写这里,组件挂载完毕执行')
return ()=>{
console.log('写这里,组件卸载前执行。。')
}
},[number]) //是[]时候,相当于componentDidMount,[]里的number代表限定number改变才触发
//参数二里的值表示此值改变会触发生命周期,没写的值在变化时不触发生命周期
//useRef hook
const myRef = React.useRef()
return (
<div>
<input ref={myRef}>
<button onclick={show}></button>
</div>
)
}
context(祖孙)
const {provide,mudom} = createContext()
父:
<provide value={this.state.count}>
子
<mudom>
{(value)=>this.context.value}
</mudom>
pureComponent
替换Component,只有state和prop数据变化后才调用对应组件的render
插槽
方式一:
<parent>
<child></child>
</parent>
class parent extends Components{
render(){
{this.props.childen}
}
}
方式二:可传值
<parent render={(value)=><child myValue = value>}>
</parent>
class parent extends Components{
render(){
{this.props.render(this.state.value)}
}
}