基础知识整理开源仓库 :)欢迎star
ES7 React/Redux/GraphQL/React-Native snippets
npm i -g create-react-app
create-react-app 项目文件夹名
const element = Hello, world!
;
这种看起来可能有些奇怪的标签语法既不是字符串也不是 HTML。react
依赖import React from "react"
{/*
我是标题哦
一个有味道的标签
*/}
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')
)
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环境,内层是个对象,用于表示样式import 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>
)
}
}
Btn.jsimport styled from 'styled-components'
var Btn = styled.button`
background: #F60;
border-radius: 5px;
width: 100px;
line-height: 30px;
color: skyblue;
outline: none;
`
export {
Btn
}
函数式组件
通过自定义属性传参
<App a="我是参数"/>
或直接函数实参传参
App({a:"我是参数"})
通过函数内置参数接受
var App = (props) =>{
return (
// 你好!{props.a}
// 组件嵌套
父向子通信
)
}
类组件
自定义属性传参
<Test a="我是参数"/>
通过this.props.xxx
接收
// 构建类组件
class Test extends Component {
render(){
// React里会自己实例化,this指向那个实例
return (
我是一个类组件{this.props.a}
)
}
}
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: '我也是默认值'
}
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,
};
export default class Title extends Component {
// 1.
// state = {
// isLog: false
// }
constructor () {
super()
// 必须要继承一下父类的属性
// 2.
this.state = {
isLog: false
}
}
render() {
return (
<p>state数据</p>
)
}
}
// 直接修改
this.state.isLog = true;
console.log(this.state.isLog)
setState设置statethis.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>
)
})
//相当于
// [
// - 1
,
// - 2
,
// - 3
// ]
}
</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: '我是富文本
'
}
}
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.html
var 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
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>
)
}
}
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})
}
}
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 (
// -
// key={i}
// onClick={this.clickHandler}
// >
// {el}
//
<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})
}
}
带你玩转组件通信
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 (
生命周期展示组件
a:{this.state.a}
)
}
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()
组件被移除时执行
通常用来清除定时器,或取消一些订阅事件
// 16版本前
export default class Ref1 extends Component {
render() {
return (
{this.oDiv = dom}}>
我是一个类组件
)
}
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)
}
}
<>>
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>
)
}
}
定义函数式组件时,不好用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
会对传入的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>
)
}
}
函数柯里化 高阶函数
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 (
<>
XXX公司,版权所有
© 川阮biu
>
)
}
}
}
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)
使用customize-cra
https://www.npmjs.com/package/customize-cra
https://3x.ant.design/docs/react/use-with-create-react-app
npm i customize-cra react-app-rewired -D
const {
// 必需
override,
// 开启装饰器
addDecoratorsLegacy
} = require("customize-cra");
module.exports = override(
// enable legacy decorators babel plugin
addDecoratorsLegacy()
);
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-scripts eject"
}
操作不可逆
npm run eject
默认是使用sass预编译语法,想改成less的话
改下文件 config/webpack.config.js
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()
}
}
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':''
}
}
}
集中式状态管理工具 类似于 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数据更新)
import { createStore } from "redux"
// 创建一个仓库
// 引入 reducer
import reducer from "./reducer"
const store = createStore(reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)
export default store
npm i redux -S
src/store/index.js
import { createStore } from 'redux'
import reducer from './reducer' //store管理系统
const store = createStore(reducer)
reducer.js
const defaultState = {
arr:xxx
}
//定义管理系统
const reducer = (state=defaultState,action)=>{
return 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>
)
}
}
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
上面的方法中,会发现一些问题
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
}
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
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))
保留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/todoList/
actionCreator.js
actionTypes.js
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
}
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)
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
安装
npm i react-router-dom -S
react中万物皆组件
学习react路由中比较重要的几个组件
用来包裹根组件
配置路由规则
解决贪婪匹配问题,会显示多个路由
实现重定向功能,404页面,根路由重定向到首页
渲染到页面为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
12优缺点 显式传参(地址栏显示),刷新不消失
34优缺点 隐式传参(不在地址栏上显示),刷新消失
<Route path="/detail/:id" component={Detail}/>
组件中使用
console.log(this.props.match.params)//{id: "18"}
地址栏显示
http://localhost:3000/#/detail/18
<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"}
to
属性可以接受两种类型的参数String
、Object
<Link to={{pathname:'/cart',params:{a:1}}}>到购物车</Link>
// 组件中使用
console.log(this.props.location.params) //{a:1}
<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)
使用高阶组件withRouter
import {withRouter} from 'react-router-dom'
...
export default withRouter(组件名)
使用Route组件的属性render
,属性值为一个函数
react比较灵活,所有东西要自己写
<Route path="/my" render={(routeProps)=>{
// console.log(routeProps)
return (
<>
{
1===1?
//
<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);}
}