基础知识整理开源仓库 :)欢迎star
VSCode React插件
ES7 React/Redux/GraphQL/React-Native snippets
react特点
- 灵活,功能都要自己写,如(动态class用三目)
- Fiber 算法 将diff算法优化
- 有虚拟DOM
- jsx语法糖
- 单向数据流
- 组件系统(万物皆为组件)
react脚手架工具
- 安装全局脚手架工具
npm i -g create-react-app
- 创建项目
create-react-app 项目文件夹名
jsx
- React 使用 JSX 来替代常规的 JavaScript。
- JSX 是一个看起来很像 XML 的 JavaScript 语法扩展。
这种看起来可能有些奇怪的标签语法既不是字符串也不是 HTML。const element = <h1>Hello, world!</h1>;
它被称为 JSX, 一种 JavaScript 的语法扩展。 我们推荐在 React 中使用 JSX 来描述用户界面。 - 这个语法糖需要
react
依赖import React from "react"
- 原理
JSX-使用React构造组件=>babel编译=>js对象=>reactDom.render()=>dom元素=>插入到页面{/* <div className="title"> <h1>我是标题哦</h1> <p id="ppp">一个有味道的标签</p> </div> */} class Title extends Component { render () { return ( React.createElement('div',{className:'title'}, [React.createElement('h1',null,'我是标题哦'), React.createElement('p',{id:'app'},'一个有味道的标签')] ) ) } }
定义组件
- 函数式组件
import React from 'react' function App () { // ()是为了return后面写的代码可换行 return ( <h1>你好,React</h1> ) } export default App
- 类组件
import React,{Component} from 'react' class Content extends Component{ render () { return ( <h2>我是内容组件</h2> ) } } export default Content
使用组件(注意只能有一个根组件)
import React from "react"
import reactDom from "react-dom"
// 导入组件
import App from "./App"
import Content from './Content'
// 类组件引入直接使用,React会自动进行实例化,调用实例的方法
// var content = new Content()
// content.render()
reactDom.render(
// 这样直接调用,但写法太low了
// App(),
// 使用jsx语法糖,组件名首字母必须大写
<App/>,
document.querySelector('#root')
)
React中写样式/类名
- 外联样式
import './myStyle.css' // 直接导入css文件,记住就算是当前路径下也要加'./' // 他没有像vue的scoped局部作用域,它是全局的,所以使用时最好给每个组件的根元素设置独一无二的id或类名 class Style1 extends Component { render () { return ( // react提供`className`属性表示类名,因为class是js关键字 <div className="box"> 我是div盒子 </div> ) } }
- 内联样式
style="xxx"
直接写会报错,当多个属性是要有;
,编译成js对象会报错- 应该使用
{{}}
,外层{}
提供js环境,内层是个对象,用于表示样式
Btn.jsimport React,{Component} from 'react' import './Style.css' import ClassNames from 'classnames' import {Btn} from './Btn' // 01. 动态class/style // 02. 使用classnames库 // 03. 使用styled-components(React中万物皆为组件) export default class Style2 extends Component { render () { return ( <div style={ {fontSize: "20px", background: 'pink', color: 'skyblue', width: '200px' ,height: '200px',lineHeight: '200px',textAlign:'center'} }> 我是一个div <p style={ 1<2?{color: 'green'}:{color: 'red'} }> 我是哪个? </p> <p className={1>2?'green':'red'}> 我吃 </p> <p className={ClassNames('box',{red: 1<2})}> 使用classnames库 </p> {/* 使用styled-components */} <Btn>我是Btn组件</Btn> </div> ) } }
styled-components写法有点怪import styled from 'styled-components' var Btn = styled.button` background: #F60; border-radius: 5px; width: 100px; line-height: 30px; color: skyblue; outline: none; ` export { Btn }
props
- 组件传参
-
函数式组件
通过自定义属性传参<App a="我是参数"/>
或直接函数实参传参
App({a:"我是参数"})
通过函数内置参数接受
var App = (props) =>{ return ( // <h1>你好!{props.a}</h1> // 组件嵌套 <div> <Fa a="父向子通信1"> <h1>父向子通信</h1> </Fa> <Content/> </div> ) }
-
类组件
自定义属性传参<Test a="我是参数"/>
通过
this.props.xxx
接收// 构建类组件 class Test extends Component { render(){ // React里会自己实例化,this指向那个实例 return ( <h1> 我是一个类组件{this.props.a} </h1> ) } }
- 单向数据流
- React是单向数据流,数据主要从父节点传递到子节点(通过props)。容器组件向傻瓜组件传参。状态提升:将子组件中的数据都提升到公共的祖先组件中管理。
- 如果顶层(父级)的某个props改变了,React会重渲染所有的子节点。
- 不可以使用this.props直接修改props,因为props是只读的,props是用于整个组件树中传递数据和配置。
- 定义默认属性
跟js里面类定义静态属性的写法一样import React, { Component } from 'react' export default class DefaultProp extends Component { // static defaultProps = { // title: "我是默认值" // } render() { return ( <div className="prop"> {this.props.title} </div> ) } } DefaultProp.defaultProps = { title: '我也是默认值' }
- props验证
安装依赖prop-types
使用npm i prop-types
import React,{Component} from 'react' import PropTypes from 'prop-types' export default class PropCheck extends Component { static defaultProps = { title: '我是默认的标题' } render () { return ( <div className="checkProp"> {this.props.title} </div> ) } } PropCheck.propTypes = { // title: PropTypes.string, // title: PropTypes.string, // b: PropTypes.string.isRequired, // obj: PropTypes.object, // arr: PropTypes.array, // boo: PropTypes.bool, fun: PropTypes.func, };
state
- 组件的局部状态,挂载数据
- 定义state有两种方法
export default class Title extends Component { // 1. // state = { // isLog: false // } constructor () { super() // 必须要继承一下父类的属性 // 2. this.state = { isLog: false } } render() { return ( <p>state数据</p> ) } }
- 更改state
如果直接更改state里面的数据,数据会改,但组件中使用的数据 值不会变化
setState设置state// 直接修改 this.state.isLog = true; console.log(this.state.isLog)
this.setState({a:1},()=>{console.log('异步更改state完成了')}) this.setState((prevState,props)=>{a:1},()=>{console.log('异步更改state完成了')})
- 其他
状态提升、受控组件和非受控组件、有状态组件与无状态组件
数据渲染
- 列表渲染
constructor () { super() this.state = { arr: [1,2,3,4,5] } } render() { return ( <div> {/* 列表渲染 */} { this.state.arr.map((el,index)=>{ return ( <li key={index}>{el}</li> ) }) //相当于 // [ // <li key='1'>1</li>, // <li key='2'>2</li>, // <li key='3'>3</li> // ] } </div> ) }
- 条件渲染
constructor () { super() this.state = { isChecked: false, button: 0, } } render() { return ( <div> {/* 条件渲染 */} { this.state.isChecked? '已选中': '未选中' } { this.state.button? <button>按钮二</button>: <button>按钮一</button> } </div> ) }
- 富文本渲染
constructor () { super() this.state = { richStr: '<h1>我是富文本</h1>' } } render() { return ( <div> {/* 富文本渲染 */} { <Fragment> <div dangerouslySetInnerHTML={{__html:this.state.richStr}}> </div> </Fragment> } </div> ) }
组件间通信
-
props传参
-
组件嵌套(类似于vue中的插槽)
传参<Fa a="父向子通信1"> <h1>父向子通信</h1> </Fa>
接收
this.props.xxx
this.props.children
class Fa extends Component { render () { return ( <div>我是父组件 <div> {this.props.children} </div> <p>{this.props.a}</p> </div> ) } }
-
在父组件中定义用于改变自身state的方法,通过props传递给子组件,因为复杂数据类型是地址引用,还是父组件自己改变数据的
-
自定义事件
注意:在 componentDidMount中注册事件,在componentWillUnmount中取消事件注册。
摘自https://www.cnblogs.com/yaoyinglong/p/7806721.htmlvar EventEmitter = require('events').EventEmitter; import React,{Component} from 'react'; import ReactDOM from 'react-dom'; let emitter = new EventEmitter(); class ListItem extends Component{ static defaultProps = { checked: false }; constructor(props){ super(props); } render(){ return ( <li> <input type="checkbox" checked={this.props.checked} onChange={this.props.onChange} /> <span>{this.props.value}</span> </li> ); } } class List extends Component{ constructor(props){ super(props); this.state = { list: this.props.list.map(entry=>({ text:entry.text, checked:entry.checked || false })) }; console.log(this.state); } onItemChange(entry){ const {list} = this.state; this.setState({ list:list.map(prevEntry=>({ text: prevEntry.text, checked:prevEntry.text === entry.text? !prevEntry.checked : prevEntry.checked })) }); //这里触发事件 emitter.emit('ItemChange',entry); } render(){ return ( <div> <ul> {this.state.list.map((entry,index)=>{ return (<ListItem key={`list-${index}`} value = {entry.text} checked = {entry.checked} onChange = {this.onItemChange.bind(this, entry)} />); })} </ul> </div> ); } } class App extends Component{ constructor(props){ super(props); } componentDidMount(){ this.itemChange = emitter.addListener('ItemChange',(msg,data)=>console.log(msg));//注册事件 } componentWillUnmount(){ emitter.removeListener(this.itemChange);//取消事件 } render(){ return ( <List list={[{text:1},{text:2}]}/> ) } } ReactDOM.render( <App/>, document.getElementById('root') );
-
redux
事件
- react里面的事件是合成事件,遵循驼峰命名法
- 事件函数使用有三种方法:
- 直接
onClick = {() => {}}
不推荐,因为一般视图上最好不要写太多业务逻辑,不利于代码的后期维护import React, { Component } from 'react' export default class MyEvent extends Component { constructor () { super() this.state = { isLiked : false } } render() { return ( <div className="my_event"> <button onClick= {()=>{this.setState({isLiked: !this.state.isLiked})}}>切换</button> <p> {this.state.isLiked?'喜欢你':' 讨厌你'} </p> </div> ) } }
- 以属性的形式定义事件函数,箭头函数获取上下文的this
import React, { Component } from 'react' export default class MyEvent extends Component { constructor () { super() this.state = { isLiked : false } } render() { return ( <div className="my_event"> <button onClick={this.clickHandler}>切换</button> <p> {this.state.isLiked?'喜欢你':' 讨厌你'} </p> </div> ) } clickHandler = () => { this.setState({isLiked: !this.state.isLiked}) } }
- 以方法的形式定义事件函数,通过bind改变this的指向;定义属性来接受bind生成的新函数,使用这个属性
import React, { Component } from 'react' export default class MyEvent extends Component { constructor () { super() this.state = { isLiked : false } this.clickHandler = this.clickHandler.bind(this) } render() { return ( <div className="my_event"> <button onClick={this.clickHandler}>切换</button> <p> {this.state.isLiked?'喜欢你':' 讨厌你'} </p> </div> ) } clickHandler () { // 一般来说事件函数里的this是指向事件源的,但框架里面的事件都是重新封装的,里面通常想拿到的是实例 // react里面直接拿它就给个undefined,你需要手动通过bind的来改变this的指向 // 但bind每次执行会生成新的函数副本,通常我们定义个属性来保存这个函数,使用那个属性就行了 // console.log(this) this.setState({isLiked: !this.state.isLiked}) } }
- 事件对象
事件函数的内置参数import React, { Component } from 'react' import './eventObj.css' export default class EventObj extends Component { constructor () { super() this.state = { arr : [1,2,3,4,5] } } render() { return ( <div> {this.state.arr.map((el,i)=>{ return ( // <li // key={i} // onClick={this.clickHandler} // > // {el} // </li> <li key={i} onClick={(e) => {this.clickHandler1(e,i)}} > {el} </li> ) })} </div> ) } // 事件函数的第一个参数是事件对象 clickHandler = (e) => { // console.log(e) // 获取事件源 var target = e.target //给点击的元素加个类名(背景颜色样式) target.classList.add('greenBg') } // 事件函数传参 // 注意传参时,用函数包一下,不然他会直接调用,包它的函数才是事件函数 clickHandler1 = (e,i) =>{ console.log(e.target,i) } }
- 练习:实现表单元素的双向数据绑定
// input双向数据绑定实现 import React, { Component } from 'react' export default class InputBind extends Component { constructor () { super() this.state={ inputTxt : '初始值' } } render() { return ( <div> <input type="text" value={this.state.inputTxt} onChange={this.inputHandler}/> {this.state.inputTxt} </div> ) } inputHandler = (e) => { this.setState({inputTxt: e.target.value}) } }
TodoList案例
带你玩转组件通信
src/todoList/index.js
import React, { Component } from 'react'
import TodoHeader from "../todoHeader"
import TodoContent from "../todoContent"
export default class TodoList extends Component {
constructor () {
super()
this.state = {
todoList : [{
content: '123',
isComplated: false
}],
inputTxt: ''
}
}
render() {
return (
<div className="todoList">
{/* 将方法通过自定义属性传过去 */}
<TodoHeader inputTxt={this.state.inputTxt} inputTxtHandler={this.inputTxtHandler} addHandler={this.addHandler}/>
<TodoContent list={this.state.todoList} finishHandler={this.finishHandler} delHandler={this.delHandler}/>
</div>
)
}
inputTxtHandler = (txt) => {
this.setState({
inputTxt: txt
})
}
addHandler = () => {
let {todoList,inputTxt} = this.state
todoList.push({
content: this.state.inputTxt,
isComplated: false
})
this.setState({
todoList,
inputTxt:''
})
}
finishHandler = (i) => {
let {todoList} = this.state
todoList[i].isComplated = true
this.setState({
todoList
})
}
delHandler = (i) => {
let {todoList} = this.state
todoList.splice(i,1)
this.setState({
todoList
})
}
}
src/todoHeader/index.js
import React, { Component } from 'react'
export default class TodoHeader extends Component {
constructor () {
super()
this.addHandler = this.addHandler.bind(this)
}
render() {
return (
<div>
<input value={this.props.inputTxt} onChange={this.inputHandler}/>
<button onClick={this.addHandler}>添加</button>
</div>
)
}
inputHandler = (e) =>{
let target = e.target
// 直接将父组件的函数传过来,引用数据类型是地址传递,我们只需要在自组件中调用父组件的方法就行了
// 只是将值传过去,操作方面的还是在父组件中操作
this.props.inputTxtHandler(target.value)
}
addHandler(){
this.props.addHandler()
}
}
src/todoItem/index.js
import React, { PureComponent } from 'react'
export default class TodoItem extends PureComponent {
constructor () {
super()
this.finish = this.finish.bind(this)
this.del = this.del.bind(this)
}
render() {
console.log("渲染了")
return (
<div>
{this.props.content}
<input readOnly style={{width:'40px'}} value={this.props.isComplated?'已完成':'未完成'}/>
<button onClick={this.finish}>完成</button>
<button onClick={this.del}>删除</button>
</div>
)
}
finish(){
this.props.finish(this.props.index)
}
del(){
this.props.del(this.props.index)
}
}
src/todoContent/index.js
import React, { Component } from 'react'
import TodoItem from '../todoItem'
export default class TodoContent extends Component {
// 爷孙通信
// 爷爷组件将自己的方法通过prop传给父组件,父组件再通过prop传给子组件,传的是复杂数据类型,是地址传递
render() {
return (
<div>
{
this.props.list.map((el,i)=>{
return (
<TodoItem key={i} del={this.props.delHandler} content={el.content} isComplated={el.isComplated} index={i} finish={this.props.finishHandler} />
)
})
}
</div>
)
}
// shouldComponentUpdate(nextProps, nextState){
// console.log(JSON.stringify(this.props.list),JSON.stringify(nextProps.list))
// if(JSON.stringify(this.props.list) === JSON.stringify(nextProps.list)){
// return false
// }else{
// return true
// }
// }
}
生命周期
挂载
constructor()
初始化构造函数
static getDerivedStateFromProps()
定义state(基于外部的props),接受参数props,需要内部返回{}
生成的state会合并当前的state
export default class Life extends Component {
render() {
return (
<div>
生命周期展示组件
a:{this.state.a}
</div>
)
}
static getDerivedStateFromProps(props){
return{
a: props.aa*2
}
}
}
render()
渲染 生成虚拟dom
componentDidMount()
渲染完成 可以获取真实Dom
初始化数据,发送ajax在这里发送
类库实例刷新
betterScroll refresh()
swiper observer:true/update()/newSwiper
更新
static getDerviedStateFromProps()
数据(props,state)更新时会再次执行这个函数,即生成的state会随外部传入的props变化而变化
shouldComponentUpdate()
两个参数,nextProps,nextState
用于优化性能,比较更新前后的数据,有bug
通常使用PrueComponent来代替它,但PureComponent只能进行浅比较
render()
getSnapshotBeforeUpdate()
返回快照(更新后的虚拟dom)
componentDidUpdate()
三个参数prevProps, prevState, snapshot
组件数据更新且渲染完成后,可获取最新的dom
卸载
componentWillUnmount()
组件被移除时执行
通常用来清除定时器,或取消一些订阅事件
ref获取元素
// 16版本前
export default class Ref1 extends Component {
render() {
return (
<div ref={(dom)=>{this.oDiv = dom}}>
我是一个类组件
</div>
)
}
componentDidMount(){
console.log(this.oDiv)
}
}
// 16版本后
import React, { Component, createRef } from 'react'
export default class Ref2 extends Component {
constructor () {
super()
// 创建容器
this.oDiv = createRef()
}
render() {
return (
<div>
<div ref={this.oDiv}>
我也是一个类组件1
</div>
<div ref={this.oDiv}>
我也是一个类组件2
</div>
</div>
)
}
componentDidMount(){
// 一个容器里只能放一个
console.log(this.oDiv.current)
}
}
不会渲染的标签
<Fragment></Fragment>
<></>
Context
Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。
使用方法:1. 定义context对象 2. 将根组件放在对象的属性Provider组件中,value属性定义提供的数据 3. 在组件使用数据的地方,使用对象的属性Consumer组件,在这组件中以函数的形式返回数据
import React, { Component, createContext } from 'react'
// 当组件所处的树中没有匹配到provider时,读取默认值;否则读取最近的provider,类似于作用域链
const context1 = createContext("我是默认数据")
var A = ()=>{
return (
<div>
我是A组件
</div>
)
}
var B = ()=>{
return (
<div>
我是B组件
<context1.Consumer>
{
(v)=>{
return v
}
}
</context1.Consumer>
</div>
)
}
export default class Context1 extends Component {
render() {
return (
<>
<context1.Provider value={"我是公共数据"}>
<A/>
<B/>
</context1.Provider>
</>
)
}
}
通常使用时进行模块化管理
/Context2/Context2.js
import React, { Component, createContext } from 'react'
let context2 = createContext("我是默认值,组件没有provider包裹")
let {Provider,Consumer: MyConsumer} = context2
// 找到属性Consumer赋值给MyConsumer变量
export default class Context2 extends Component {
constructor(){
super()
this.state = {
a: 10,
b: 20
}
}
act = () =>{
this.setState(()=>{
return{
a:20
}
})
}
render() {
return (
<Provider value={{...this.state, act: this.act}}>
{this.props.children}
</Provider>
)
}
}
export {
Context2,
MyConsumer
}
index.js
包裹根元素
import React from 'react'
import ReactDom from 'react-dom'
import App from './App'
import { Context2 } from './Context2/Context2'
ReactDom.render(
<Context2>
<App/>
</Context2>,
document.querySelector('#root')
)
组件中使用数据
Context2/B.js
import React, { Component } from 'react'
import { MyConsumer } from './Context2'
export default class B extends Component {
render() {
return (
<div>
我是B组件
<MyConsumer>
{
({a,act})=>{
return (
<>
<p>{a}</p>
<button onClick={act}>Change</button>
</>
)
}
}
</MyConsumer>
</div>
)
}
}
React-Hooks
定义函数式组件时,不好用State和生命钩子函数
state的使用
useState(数据)
// rafce
import React,{useState} from 'react'
// 函数式组件中要想使用state或生命钩子函数
// 必须通过react-hooks
const A = (props) => {
// console.log(useState(1)) //返回值是一个数组[1,fn]
// 结构一下
let [num,setNum] = useState(1)
return (
<div>
我是A函数组件
{/* 注意要前置运算,里面是赋值操作 */}
<br/>
<button onClick={()=>{setNum(++num)}}>+</button>
<span>{num}</span>
<button onClick={()=>{setNum(--num)}}>-</button>
</div>
)
}
export default A
生命钩子函数(componentDidMount,componentDidUpdate)
useEffect(()=>{})
import React,{useEffect,useState} from 'react'
const B = () => {
// 初始化arr和接受setArr函数
let [timer,setTimer] = useState(1)
let [arr,setArr] = useState([])
useEffect(()=>{
console.log('相当于组件的componentDidMount和componentDidUpdate...')
if(timer === 1){
setTimeout(()=>{
setTimer(null)
setArr([1,2,3,4,5])
},3000)
}
})
return (
<div>
<ul>
{arr.map((el,i)=>{
return (
<li key={i}>{el}</li>
)
})}
</ul>
</div>
)
}
export default B
使用context
useContext(context对象)
contexts.js
导出Provider组件和context对象
import React, { Component, createContext } from 'react'
let myContext = createContext("组件树上没有Provider时显示的默认值")
let {Provider} = myContext
class Contexts extends Component {
constructor(){
super()
this.state = {
a: 123
}
}
render() {
return (
<Provider value={this.state}>
{this.props.children}
</Provider>
)
}
}
let MyProvider = Contexts
export {
MyProvider,
myContext
}
index.js
包裹根元素
import {MyProvider} from './contexts/Contexts'
ReactDom.render(
<MyProvider>
<App/>
</MyProvider>,
document.querySelector('#root')
)
C.js
使用value
import React,{useContext} from 'react'
import {myContext} from '../contexts/Contexts'
const C = () => {
let {a} = useContext(myContext)
return (
<div>
我是C组件
{a}
</div>
)
}
export default C
PureComponent
会对传入的props进行浅比较,优化性能
import React, { PureComponent } from 'react'
export default class ListItem extends PureComponent {
render() {
console.log('渲染了')
return (
<div>
<h2>{this.props.name}</h2>
<p>{this.props.age}</p>
</div>
)
}
}
HOC(高阶组件)
函数柯里化 高阶函数
function fn(a){
return function(b){
return a+b
}
}
fn(1)(2) // 3
其实高阶组件本质就是函数,接受一个参数(被修饰组件),返回一个组件
import React, { Component } from 'react'
let Hoc = (Dec) => {
return class HOC extends Component {
render() {
return (
<>
<Dec/>
<h3>XXX公司,版权所有</h3>
<p>© 川阮biu</p>
</>
)
}
}
}
export default Hoc
News组件中使用
import React, { Component } from 'react'
import Hoc from './HOC'
class News extends Component {
render() {
return (
<div>
偶是新闻页
</div>
)
}
}
export default Hoc(News)
ES6中的修饰器
https://es6.ruanyifeng.com/#docs/decorator
使用@函数名
@函数名
class xxx {
...
}
export default xxx
等价于 export default 函数名(xxx)
在cra中进行非弹出(eject)配置
使用customize-cra
https://www.npmjs.com/package/customize-cra
https://3x.ant.design/docs/react/use-with-create-react-app
- 安装customize-cra
npm i customize-cra react-app-rewired -D
- 在项目文件夹下创建config-overrides.js文件夹
在这文件里写配置代码
const {
// 必需
override,
// 开启装饰器
addDecoratorsLegacy
} = require("customize-cra");
module.exports = override(
// enable legacy decorators babel plugin
addDecoratorsLegacy()
);
- 修改package.json里面的脚本命令
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-scripts eject"
}
弹射webpack配置
操作不可逆
npm run eject
默认是使用sass预编译语法,想改成less的话
改下文件 config/webpack.config.js
axios使用
- 直接导入使用
import React, { Component } from 'react'
import axios from 'axios'
export default class List extends Component {
constructor(){
super()
this.state = {
arr: []
}
}
render() {
return (
<>
<ul>
{
this.state.arr.map((el,i)=>{
return (
<li key={i}>
<h4>{el.name}</h4>
<p>id:{el.id}</p>
</li>
)
})
}
</ul>
</>
)
}
fetchList(){
axios.get('https://api.it120.cc/hundan/shop/goods/category/all').then((res)=>{
this.setState({
arr: res.data.data
})
}).catch((err)=>{
console.log(err)
})
}
componentDidMount(){
console.log('组件挂载完成时调用')
this.fetchList()
}
}
- 通过原型链生成属性到Component上
但只能在类组件中通过this.$http使用
import React,{Component} from 'react'
import axios from 'axios'
Component.prototype.$http = axios
类组件中使用
fetchList(){
this.$http.get('xxx').then((res)=>{})
}
配置反向代理(非弹射下)
-
直接在package.json中配置
“proxy”: “https://api.it120.cc/hundan”
但这只能写一行 -
使用
http-proxy-middleware
https://www.npmjs.com/package/http-proxy-middleware
安装
npm install --save-dev http-proxy-middleware
在src文件夹下创建setupProxy.js文件,在这文件中配置代理
const {createProxyMiddleware: proxy} = require("http-proxy-middleware")
module.exports = function(app){
app.use(proxy('/baidu',{
target: "https://news.baidu.com/",
pathRewrite: {'^/baidu': ''},
changeOrigin: true
}));
app.use(proxy('/api',{
target: "https://www.fakin.cn/",
pathRewrite: {'^/api': ''},
changeOrigin: true
}));
app.use(proxy('/hundan',{
target: "https://api.it120.cc/hundan",
pathRewrite: {'^/hundan': ''},
changeOrigin: true
}));
}
配置反向代理(弹射下)
在 config/webpack.config.js文件中修改proxy属性
proxy: {
'/hundan': {
target: 'https://api.it120.cc',
ws: false,
changeOrigin: true,
pathRewrite: {
'^hundan':''
}
}
}
redux
集中式状态管理工具 类似于 vuex
项目 比较 大 (数据 比较 复杂)
注意:
vuex是基于vue
redux 是和 react 解耦的(可以在任意一个库中使用)
https://www.redux.org.cn/ 中文文档
每一次处理数据,
先reducer调用,调用时,store会将state作为参数传入,一般还有行为(action),action会未作为第二个参数传入,reducer 会根据action 处理 state(store传入的形参)
处理state时,需要 深拷贝(state是store传入,state是对象,传递的是引用),
reducer根据action改变深克隆的这个 state 然后返回给store,store自己去更新数据
注意:
每一次不管是拿数据 还是 提交数据 还是改变数据
组件 向仓库 提交 action store 都会先调用管理系统 reducer,将现在的state,和action都作为参数 给reducer reducer 根据action.type去修改 state(深克隆的),改完后将值返回给 store store再存起来(state数据更新)
redux-dev-tools
import { createStore } from "redux"
// 创建一个仓库
// 引入 reducer
import reducer from "./reducer"
const store = createStore(reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)
export default store
如何 创建一个 store
npm i redux -S
src/store/index.js
import { createStore } from 'redux';
import reducer from './reducer'; //store管理系统
const store = createStore(reducer);
src/store/reducer.js
const defaultState = {
arr:xxx
};
//定义管理系统
const reducer = (state=defaultState,action) => {
return state;
};
export default reducer;
组件中如何 使用 store
- 获取state
store.getState()
import React, { Component } from 'react'
import store from './store'
export default class News extends Component {
constructor(){
super()
this.state = store.getState()
}
render() {
return (
<div>
我是新闻页
<ul>
{
this.state.arr.map((el,i)=>{
return (
<li key={i}>{el}</li>
)
})
}
</ul>
</div>
)
}
}
- 设置state
store.dispatch(action)
通过调用store.dispatch()
提交action
,他会再次触发reducer
,根据action
里面的type
做相应处理,更改state
时,要进行深拷贝,不能直接更改state
,应该返回个新的state
让store
自己更改。注意,返回一个新的state
后,store
将state
更改了,而我们在constructor
里面使用的state
是通过store.getState()
获取的,constructor
这个生命周期钩子函数只会执行一遍,我们要想获取最新的state
,可以使用store.subscribe(()=>{'store里的state更改了'})
action
格式是一个对象,通常设置属性type
表示什么行为,data
或value
保存action中携带的数据
下面以todoList案例介绍store使用
src/TodoList.js
import React, { Component } from 'react'
import store from './store'
import './myStyle.scss'
export default class TodoList extends Component {
constructor(){
super()
this.state = {
inputTxt:'',
...store.getState()
}
store.subscribe(this.updater)
}
render() {
return (
<div>
<header>
<input type="text" placeholder="请输入..." value={this.state.inputTxt} onChange={this.inputHandler}/>
<button onClick={this.clickHandler}>增加</button>
</header>
<ul className="list">
{
this.state.arr.map((el,i)=>{
return (
<li key={i}>
{el.content}
<button onClick={()=>{this.delHandler(i)}}>删除</button>
<button onClick={()=>{this.changeHandler(i)}}>{el.isComplated?'已完成':'未完成'}</button>
</li>
)
})
}
</ul>
</div>
)
}
updater = ()=>{
this.setState({
...store.getState()
})
}
inputHandler = (e) =>{
let target = e.target
this.setState({
inputTxt: target.value
})
}
clickHandler = () =>{
store.dispatch({
type: 'addListItem',
value: this.state.inputTxt
})
this.setState({
inputTxt: ''
})
}
delHandler = (index) => {
// react里面由confirm方法,所以要加window.,别省略了
if(window.confirm('确定删除吗?')){
store.dispatch({
type: "delListItem",
index
})
}
}
changeHandler = (index) => {
store.dispatch({
type: 'changeListItem',
index,
state: !this.state.arr[index].isComplated
})
}
}
src/store/index.js
import {createStore} from 'redux'
import reducer from './reducer'
let store = createStore(reducer)
export default store
src/store/reducer.js
let defaultState = {
arr: []
}
let reducer = (state=defaultState,action) => {
//注意要深拷贝,不能直接修改state
let newState = JSON.parse(JSON.stringify(state))
switch(action.type){
case "addListItem":
newState.arr.push({
content: action.value,
isComplated: false
})
break;
case "delListItem":
newState.arr.splice(action.index,1)
break;
case "changeListItem":
newState.arr[action.index].isComplated = action.state
break;
default:
break;
}
return newState
}
export default reducer
上面的方法中,会发现一些问题
- action直接写在dispatch中,不方便复用,通常以函数返回对象的形式定义action,模块化管理这些action
- type是字符串,书写时可能出错,通常写成常量
src/store/actionCreator.js
import {ADD_LISTITEM,DEL_LISTITEM,CHANGE_LISTITEM} from './actionType'
const addListItem = (txt) => {
return {
type: ADD_LISTITEM,
value: txt
}
}
const delListItem = (index) => {
return {
type: DEL_LISTITEM,
index
}
}
const changeListItem = ({index,state}) => {
return {
type: CHANGE_LISTITEM,
index,
state
}
}
export {
addListItem,
delListItem,
changeListItem
}
src/store/actionType.js
var ADD_LISTITEM = 'addListItem';
var DEL_LISTITEM = "delListItem";
var CHANGE_LISTITEM = "changeListItem";
export {
ADD_LISTITEM,
DEL_LISTITEM,
CHANGE_LISTITEM
}
异步action
redux插件实现
redux-saga (es6 generator语法)
redux-thunk 讲这个
安装redux-thunk
npm i redux-thunk -S
直接使用,不保留redux-dev-tool
src/store/index.js
import {createStore,applyMiddleware} from 'redux'
import reducer from './reducer'
import thunk from 'redux-thunk'
let store = createStore(reducer,applyMiddleware(thunk))
export default store
保留redux-dev-tool(createStore函数只接受两个函数)
import {createStore,applyMiddleware,compose} from 'redux'
import reducer from './reducer'
import thunk from 'redux-thunk'
// 组合工具函数compose
let enhancer = compose(
applyMiddleware(thunk),
(window &&window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__() : (f) => f)
let store = createStore(reducer,enhancer)
export default store
src/store/actionCreator.js
const addListItem = (txt) => {
return {
type: ADD_LISTITEM,
value: txt
}
}
// 定义异步action
const asyncAdd = (txt) =>{
// 返回的是一个函数,不是对象
// store.dispatch()会判断参数为函数的情况,这函数的参数就为dispatch
return (dispatch)=>{
setTimeout(function(){
dispatch(addListItem(txt))
},2000)
}
}
组件中使用异步action
store.dispatch(asyncAdd(this.state.inputTxt))
reducer模块化管理
src/store/reducer.js
src/store/todoList/
actionCreator.js
actionTypes.js
reducer.js
src/store/todoList/reducer.js
export default todoReducer
src/store/reducer.js
import {combineReducers} from 'redux'
import todoReducer from './todoList/reducer'
let rootReducer = combineReducers({
todoReducer
})
// store中的state会变成多层
export default rootReducer
组件中使用todoList的state
this.state = {
inputTxt:'',
...store.getState().todoReducer
}
react-redux
redux和react是解耦的,不方便使用(store.getState()、store.dispatch(action))
搭配react使用 原理:高阶组件、context
通过props使用state和dispatch action
https://www.redux.org.cn/docs/react-redux/api.html#api
npm i react-redux -S
将store中的state以props形式传递于整个组件树中
src/index.js
import React from 'react'
import ReactDom from 'react-dom'
import App from './App'
import store from './store'
import {Provider} from 'react-redux'
ReactDom.render(
<Provider store={store}>
<App/>
</Provider>,
document.querySelector("#root")
)
组件中使用state和派发action就不需要基于store了,也不需要store.subsrcibe来配置监听state的变化
为了更方便的使用dispatch和获取state,使用connect方法
src/TodoList.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { addListItem, delListItem, changeListItem, asyncAdd } from './store/todoList/actionCreator'
import './myStyle.scss'
class TodoList extends Component {
constructor(){
super()
this.state = {
inputTxt:''
}
}
render() {
return (
<div>
<header>
<input type="text" placeholder="请输入..." value={this.state.inputTxt} onChange={this.inputHandler}/>
<button onClick={this.clickHandler}>增加</button>
</header>
<ul className="list">
{
this.props.arr.map((el,i)=>{
return (
<li key={i}>
{el.content}
<button onClick={()=>{this.delHandler(i)}}>删除</button>
<button onClick={()=>{this.changeHandler(i)}}>{el.isComplated?'已完成':'未完成'}</button>
</li>
)
})
}
</ul>
</div>
)
}
inputHandler = (e) =>{
let target = e.target
this.setState({
inputTxt: target.value
})
}
clickHandler = () =>{
// store.dispatch(addListItem(this.state.inputTxt))
this.props.asyncAdd(this.state.inputTxt)
this.setState({
inputTxt: ''
})
}
delHandler = (index) => {
if(window.confirm('确定删除吗?')){
this.props.delListItem(index)
}
}
changeHandler = (index) => {
this.props.changeListItem({index,state: !this.props.arr[index].isComplated})
}
}
const mapStateToProps = (state) => {
return{
arr: state.todoReducer.arr
}
}
const mapDispatchToProps = (dispatch) => {
return {
asyncAdd: (v)=>{
dispatch(asyncAdd(v))
},
delListItem: (i)=>{
dispatch(delListItem(i))
},
changeListItem: (params)=>{
dispatch(changeListItem(params))
}
}
}
export default connect(mapStateToProps,mapDispatchToProps)(TodoList)
redux持久化存储实现
localStorage、sessionStorage
在reducer.js中进行存储localStorage
src/store/todoList/reducer.js
import {ADD_LISTITEM,DEL_LISTITEM,CHANGE_LISTITEM} from './actionType'
let storageState = localStorage.getItem('todoState')?JSON.parse(localStorage.getItem('todoState')):{arr: []}
let defaultState = storageState
let todoReducer = (state=defaultState,action) => {
let newState = JSON.parse(JSON.stringify(state))
switch(action.type){
case ADD_LISTITEM:
newState.arr.push({
content: action.value,
isComplated: false
})
break;
case DEL_LISTITEM:
newState.arr.splice(action.index,1)
break;
case CHANGE_LISTITEM:
newState.arr[action.index].isComplated = action.state
break;
default:
break;
}
localStorage.setItem("todoState",JSON.stringify(newState))
return newState
}
export default todoReducer
react路由
安装
npm i react-router-dom -S
react中万物皆组件
学习react路由中比较重要的几个组件
<BrowserRouter/>
<HashRouter/>
用来包裹根组件
<Route>
配置路由规则
<Switch>
解决贪婪匹配问题,会显示多个路由
<Redirect>
实现重定向功能,404页面,根路由重定向到首页
<Link>
<NavLink>
渲染到页面为a标签,可实现跳转指定路由的功能
exact
这是组件的一个常用属性,用于解决模糊匹配路径的问题,实现精确匹配
使用
index.js
包裹根组件,可以在整个组件树中使用路由功能,同时路由组件的props会携带一些路由信息
mport {HashRouter as Router} from 'react-router-dom'
ReactDom.render(
<Router>
<App/>
</Router>,
document.querySelector("#root")
)
App.js
import React from 'react'
// 这些组件都需要引入使用
import {Switch,Route,Redirect,Link,NavLink} from 'react-router-dom'
import Home from './Home'
import NotFound from './NotFound'
import Detail from './Detail'
import Cart from './Cart'
import News from './News'
const App = () => {
return (
<div>
<Link to="/home">到首页</Link>
<Link to="/news">到新闻页</Link>
<Link to="/cart">到购物车</Link>
<hr/>
<NavLink to="/home" activeStyle={{color: "pink"}} activeClassName="current">到首页</NavLink>
<NavLink to="/news" activeStyle={{color: "pink"}} activeClassName="current">到新闻页</NavLink>
<NavLink to="/cart" activeStyle={{color: "pink"}} activeClassName="current">到购物车</NavLink>
<Switch>
<Route path="/home" component={Home}/>
<Route path="/detail/:id" component={Detail}/>
<Route path="/cart" component={Cart}/>
<Route path="/news" component={News}/>
<Route path="/404" component={NotFound}/>
<Redirect to="/home" from="/" exact/>
<Redirect to="/404"/>
</Switch>
</div>
)
}
export default App
嵌套路由
写在父路由模板组件中
父路由组件(Route)不能加exact
属性
父路由模板组件
News.js
import React, { Component } from 'react'
import {Switch,Route} from 'react-router-dom'
import NativeNews from './NativeNews'
import AbroadNews from './AbroadNews'
export default class News extends Component {
render() {
return (
<div>
我是新闻页
<hr/>
<Switch>
<Route path="/news/nativeNews" component={NativeNews}/>
<Route path="/news/abroadNews" component={AbroadNews}/>
</Switch>
</div>
)
}
}
App.js
import React from 'react'
import News from './News'
import {Route,Redirect,Switch,Link,NavLink} from 'react-router-dom'
const App = (props) => {
console.log(props)
return (
<div>
<Link to="/news">到新闻页</Link>
<hr/>
<NavLink to="/news" activeClassName="current" activeStyle={{color:"pink"}}>到新闻页</NavLink>
<Switch>
{/* news有子路由,不可以加 exact精确查询,要先进入父组件*/}
<Route path="/news" component={News}/>
</Switch>
</div>
)
}
export default App
路由传参
- 动态路由传参
- search
- params
- state
12优缺点 显式传参(地址栏显示),刷新不消失
34优缺点 隐式传参(不在地址栏上显示),刷新消失
- 动态路由传参
<Route path="/detail/:id" component={Detail}/>
组件中使用
console.log(this.props.match.params)//{id: "18"}
地址栏显示
http://localhost:3000/#/detail/18
- search
直接地址栏后拼接
<Link to="/cart?user=Jack">到购物车</Link>
组件中获取使用
console.log(this.props.history.location.search) //?user=Jack
console.log(this.props.location.search) //?user=Jack
console.log(qs.parse(this.props.location.search.split('?')[1].toString())) //{user: "Jack"}
- params
Link和NavLink组件的to
属性可以接受两种类型的参数String
、Object
为对象时就可以设置一些参数
<Link to={{pathname:'/cart',params:{a:1}}}>到购物车</Link>
// 组件中使用
console.log(this.props.location.params) //{a:1}
- state
<Link to={{pathname:'/cart',state:{a:1}}}>到购物车</Link>
// 组件中使用
console.log(this.props.location.state) //{a:1}
编程式导航
Link、NavLink to
是声明式
组件中使用
this.props.history.go(n)
this.props.history.goBack()
this.props.history.goForward()
this.props.history.push(path, state)
this.props.history.replace(path, state)
非路由组件中props没有路由信息
使用高阶组件withRouter
import {withRouter} from 'react-router-dom'
...
export default withRouter(组件名)
登录权限判断,实现vue中的路由守卫
使用Route组件的属性render
,属性值为一个函数
react比较灵活,所有东西要自己写
<Route path="/my" render={(routeProps)=>{
// console.log(routeProps)
return (
<>
{
1===1?
// <NeedLogin history={routeProps.history}/>
<NeedLogin {...routeProps}/>
:
<Login/>
}
</>
)
}}/>
通过render
非component
实现渲染的组件props上没有路由信息,就算使用withRouter
也没用,我们必需要通过props属性传过去,render函数的参数就是routeProps
,就是路由信息,传过去就行了
<NeedLogin {...routeProps}/>
路由懒加载
原理webpack的按需引入
npm i react-loadable -S
路由中使用
src/routes/index.js
import Loadable from 'react-loadable';
import Loading from '@/components/Loading';
// 上面不需要导入了
const baseRoutes = [{
path: '/login',
component: Loadable({
loader: () => import('@/views/Login'),
loading: Loading,
})
},
{
path: '/404',
component: Loadable({
loader: () => import('@/views/NotFound'),
loading: Loading,
})
}];
自定义loading组件或使用antd的spin组件
src/components/Loading/index.js
import React, { Component } from 'react'
import './style.scss'
export default class index extends Component {
render() {
return (
<div className="bg">
<div className="dotBox">
<p>
<span></span>
<span></span>
<span></span>
<span></span>
</p>
<p>
<span></span>
<span></span>
<span></span>
<span></span>
</p>
</div>
</div>
)
}
}
样式
src/components/Loading/style.scss
.bg{
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 999;
background:rgba($color: #000000, $alpha: 0.8);
.dotBox{
width: 120px;
height: 120px;
top: 50%;
left: 50%;
margin-top: -60px;
margin-left: -60px;
position: absolute;
p{
width: 120px;
height: 120px;
position: absolute;
span{
display: block;
width: 24px;
height: 24px;
border-radius: 12px;
background: white;
position: absolute;
animation: twinkle 1.5s linear infinite;
&:nth-of-type(2){
top: 0;
right: 0;
}
&:nth-of-type(3){
bottom: 0;
right: 0;
}
&:nth-of-type(4){
bottom: 0;
left: 0;
}
}
&:nth-of-type(1){
span{
&:nth-of-type(1){
animation-delay: -0.4s;
}
&:nth-of-type(4){
animation-delay: -0.8s;
}
&:nth-of-type(3){
animation-delay: -1.2s;
}
&:nth-of-type(2){
animation-delay: -1.6s;
}
}
}
&:nth-of-type(2){
transform: rotate(45deg);
span{
&:nth-of-type(1){
animation-delay: -0.2s;
}
&:nth-of-type(4){
animation-delay: -0.6s;
}
&:nth-of-type(3){
animation-delay: -1s;
}
&:nth-of-type(2){
animation-delay: -1.4s;
}
}
}
}
}
}
@keyframes twinkle{
from{transform: scale(0);}
50%{transform: scale(1);}
to{transform: scale(0);}
}