小白初探react

react注意事项

  1. 因 为style = {} 里面传入的是对象, 所以正确的写法是 style={{fontSize: ‘12px’ …}}

  2. 在标签里面写上 事件名称 on 后面的字母是大写 , 在事件后面跟上 {} 里面传入一个 箭头函数, 里面写上逻辑业务

  3. map 数组的遍历 里面必须有return

给点击的项添加红色

// 第一种方式
style={{color: index === current ? 'red' : ''}}
// 第二种方式 在上面 style 里面上写 .red {...} 样式
className={index===curIndex?'red':''}

显示隐藏

// 通过状态动态添加类名

const List = props => {
    return (
    <ul className={flag ? 'show' : 'hide'}>
    	{props.arr.map(item => <li key={item}> {item} </li>)}
    </ul>
    )
}

// 模拟vue的v-if

function App () {
    return (
    <div>
    	<h1 onClick={() => {flag=!flag; created()}}>商品列表</h1>    
    	{flag && <List arr={app} />}  // 模拟v-if
    </div>
    )
}

遍历对象

Object.keys(obj).map(item => {
	return <p key={item}> {item}: {obj[item]} </p>
})

bind call 的区别

相同点: 都是改变this的指向
不同点: bind方法,他是直接改变这个函数的this指向并且返回一个新的函数,之后再次调用这个函数的时候this都是指向bind绑定的第一个参数
​ call会在调用之后立即执行

class Counter extends React.Component {
    constructor () {
        super()
        this.state = {
            count: 1
        }

        this.handlerClick = this.handlerClick.bind(this)  // 提前绑定好this的执行
    }

    handlerClick () {  // 写成这个普通函数的形式 内部访问的this是undefined
        this.setState({
            count: ++this.state.count
        }, ()=> {
            console.log(this.state.count)  // 拿到最新的数据
        })
        // console.log(this.state.count)  // 上面的操作是异步的
    }

    render () {
        return (
            <div>
                <p>counter ===> {this.state.count} </p>
                <button onClick={this.handlerClick}>加加</button>
            </div>
        )
    }
}

注意:上面的写法有一个问题,连续执行多次其实内部进行了对象的合并操作

// 解决?可以采用setState的第二种写法解决此类问题
this.setState((prevState)=>{  // prevState可以拿到最新this.state对象
    return {
        key:新的value值  
    }
},[,callback])

构造函数为什么方法必须写在原型上?

function Person(name, age){
    this.name = name
    this.age = age
    this.say = function () {
    console.log(this.name)
    }
}

let p = new Person('张三', 19)
let p2 = new Person('李四', 20)
p.say()  // 张三
p2.say() // 李四
console.log(p.say ===p2.say )  // false 代表每创建一个实例都会开辟一个say的函数空间

ES6继承

class Person {
    constructor (name, age) {
        this.name = name
        this.age = age
    }
    say () {
        return this.name + ',' + this.age
    }
}

class Student extends Person {
    constructor (name, age, school) {
        super(name, age)
        this.school = school
    }
    say () {
        return super.say() + ',' + this.school
    }
}

let stu = new Student('张三', 20, '谷歌')

console.log(stu.say())

受控组件

由于在表单元素上设置了 value 属性,因此显示的值将始终为 this.state.value, 这使得 React 的 state 成为唯一数据源。由于 handlechange 在每次按键时都会执行并更新 React state,因此显示的值将随着用户输入而更新

class App extends React.Component {
    constructor () {
        super()
        this.state = {
        	value: '111'
    	}
	}
    handlerChange = (e) => {
        this.setState({
        	value: e.target.value
        })
    }

    render(){
        return (
            <div>
            <input value={this.state.value} type="text" onChange={this.handlerChange} />
            <p> {this.state.value} </p>
            </div>
        )
    }
}

非受控组件

event.preventDafault() 阻止默认行为, 在表单域里阻止提交, 是针对于 form 标签, 并不是input[submit]

class App extends React.Component{

    handlerSubmit = (e) => {
        console.log(this.input)

        e.preventDefault()
	}

    render () {
        return (
            <form onSubmit={this.handlerSubmit}>
            <input ref={el=>this.input=el} type="text" />
            <input type="submit" value='提交' />
            </form>
        )
	}
}

模糊查询的内容高亮, 通过这个方法dangerouslySetInnerHTML() 将字符串解析成标签

blurFind = () => {
    let newValue = prompt('请输入要查询的字段')
    // let newDes = this.state.list.filter(item => item.indexOf(newValue) !== -1)
    let newDes = this.state.list.filter(item => item.includes(newValue))

    newDes.forEach((item, i, arr) => {
    arr[i] = item.replace(new RegExp(newValue, 'g'), '<span style="color: red">'+newValue+'</span>')
    })

    this.setState({
        des: newDes,
        flag: false
    })
}
<ul>
    {
        des.map((item, index) => {
            return 
            <li key={index}> 
            	<div dangerouslySetInnerHTML={{__html: item}}></div>
            </li>
        })
    }
</ul>

react 脚手架安装

    npm i create-react-app -g
    create-react-app --version  (3.4.1)
 
    create-react-app 项目名称
    cd 项目名称
    yarn start 启动项目

脚手架引入图片的两种方式

  // 第一种
  import Logo from "图片路径"
  <img src={Logo}/>
  // 第二种
  <img src={require("图片路径")}/>

如果只是简单的给input 绑定内容, 那么就使用官方提供的defaultValue

<form>
    <label htmlFor='hobby'>用户民: </label>
    <input id='username' type='text' defaultValue='hello' />
    <input id='hobby' type='checkbox' defaultChecked />
</form>

子父通信 , todos组件里面有一个Txt 输入框 组件 还有一个LIst组件 是用来渲染输入框里面的数据的

constructor () {
    super()
    this.state = {
    	list: []
    }
}
// 定义一个修改自身状态的方法, value是子组件调用的时候传入的参数
add = value => {
    this.setState({
    	list: [value, ...this.state.list]
    })
}
render() {
    return (
        <div>
            <Txt add={this.add} /> // 把修改自身状态的方法传递给子组件
            <List list={this.state.list} />  // 将自身的状态传递给子组件渲染
        </div>
    )
}

自定义端口号实时监听: json-server --watch 文件名 --port 端口号

上面的方式是将 两个组件公用的状态放在一个父组件中, 这样方便两个组件之间的数据共享, 这样的方式叫做状态提升, 如果不使用状态提升, 那么下面就是一个很好的例子

// 列表组件List, handlerAdd 定义一个可以修改自身状态的方法, 等待父组件去使用

    handlerAdd = (str) => {
        this.setState({
            list: [str, ...this.state.list]
        })
    }

    render() {
        let {list} = this.state
        return (
            <ul>
                {
                    list.map((item, index) => {
                        return <li key={index}> {item} </li>
                    })
                }
            </ul>
        )
    }
    
// todos 组件, 两个组件的父组件, 父组件通过ref标记 List组件, 拿到List组件的修改自身的方法, 自己再定义一个触发子组件handlerAdd的方法, 传入 Txt组件去触发
    handlerAdd = (str) => {
        this.list.handlerAdd(str)
    }

    render() {
        return (
            <div>
                <Txt add={this.handlerAdd} />
                <List ref={el=>this.list=el} />
            </div>
        )
    }
    
// Txt组件通过键盘的回车键触发父组件的传入的方法, 将文本输入框的内容当做参数传入给List组件渲染
    handler = (e) => {
        let {add} = this.props
        if(e.keyCode === 13){
            add(e.target.value)
            e.target.value = ''
        }
    }

react 代理的两种方式

// 通过yarn eject 弹射出config文件, 要不然后续如果继续安装其他模块的话,yarn内部就会进行检测看看有没有文件被改动过,重新变成原来的样子 (如果eject命令不成功, 将.git文件删掉)
proxy: {
    "/api":{
        target: 'http://47.96.0.211:9000',
        changeOrigin: true,
        pathRewrite: {
            "^/api": ''
        }
    }
}

// 再者可以通过 http-proxy-middleware中间件实现代理, 安装, 然后再src根目录下新建setupProxy文件, 里面的配置: 
const {createProxyMiddleware} = require('http-proxy-middleware')
module.exports = function (app) {
    app.use(
        createProxyMiddleware('/api', {
            target: 'http://47.96.0.211:9000',
            changeOrigin: true,
            pathRewrite: {
                "^/api": ''
            }
        })
    )
}

静态方法

class Person{
    static say(){ //静态方法 可能在实例化之前被调用
    	return "hello"
    }
}
// 只能通过这个类使用say() 方法, 实例调用这个方法会报错, 因为static 这个前缀就是静态方法的标识
coonsole.log(Person.say())	

react 挂载阶段生命周期钩子函数

// 依次执行
constructor()
static getDerivedStateFromProps(props,state)
render()
conponentDidMount()  // 该函数发起http请求

注意: 当组件的状态或者外部传入进来的属性发生改变的时候,组件的render函数会重新执行

派生状态累加的应用

// 父组件 App
constructor () {
	super()
    this.state = {
        nums: [1, 2, 3], 
        sum: 0
    }
}
// 在真实dom挂载完毕之后将 sum状态修改为数据的累加值, 相当于初始化sum的总和
componentDidMount () {
    this.setState({
    	// reduce()数组的高阶函数, 可以轻松实现累加操作
    	sum: this.state.nums.reduce((a, b) => a+b) 
    })
}
// 定义一个修改自身 sum状态的方法, 当子组件点击 +1 那么视图的sum总和将会重新渲染, 保持于子组件的步调一致, 因为子组件是通过 +1的方法让视图对应的组件 +1的, 然会传递给子组件
getTotal = p => {
    this.setState({
    	sum: this.state.sum+p
    })
}
render() {
	let {nums, sum} = this.state
	return (
        <div>
            {
            	// 通过数据的遍历渲染成三个子组件, 并将状态里的值和修改sum的方法传递子组件
                nums.map((item, index) => {
                	return <Counter getTotal={this.getTotal} key={index} n={item} />
                })
            }
            <p>{sum}</p>
        </div>
	)
}

// 子组件 counter
constructor (props) {
    super(props)
    this.state = {
        n: props.n // 根据父组件派生一个状态, 因为直接操作父组件的状态是不允许的
    }
}
handler (p) {
	// 这里判断是否小于0 如果小于0 那么就不会再-1了
    if(this.state.n > 0 || p > 0){
        this.setState({
        	n: this.state.n+p  // 修改自身的状态, 对应的视图会被重新渲染
        }, ()=>{
        	// 再修改完之后, 再调用父组件传递过来的方法, 并将具体的操作当做参数传递给父组件
            this.props.getTotal(p)
        })
    }
}
render() {
    let {n} = this.state
    return (
    <div>
        <button onClick={this.handler.bind(this, -1)}>-</button>
        {n}
        <button onClick={this.handler.bind(this, +1)}>+</button>
    </div>
    )
}

static getDerivedStateFromProp(props, state)钩子函数 (获取派生状态来自父组件的属性)

初始化的时候会执行, 当外部传入的props改变, this.setState()改变自身的状态 也会被执行

该函数内部必须要有return, 接收两个参数, props代表外部传入的最新值, state代表自身的状态

constructor (props) {
    super(props)
        this.state = {
        a: props.num, // 派生状态
        prevProps: props // 这里可以纪录一下外部传入的num值, props里面有
    }
}
static getDerivedStateFromProps (props, state) {
    const prevProps = state.prevProps
    // 这里判断上面纪录的值是否跟外部传递的值相等, 如果相等那么就让子组件的状态跟随外部传递的转递变化, 再者不相等, 是因为 props.num拿到的是最新的值, 而prevProps.num纪录的值是之前的值, 那么自身管理的a状态就是自身操作的数值
    const controlledValue = prevProps.num !== props.num ? props.num : state.a
    return {
        a: controlledValue,
        prevProps: props  // 更新纪录的值
	}
}

销毁组件触发的钩子函数 componentWillUnmount()

组件卸载的时候, 可以将componentDidMount绑定的定时器及时清除

import ReactDOM from 'react-dom'
<button onClick={()=>{ReactDOM.unmountComponentAtNode(document.getElementById('root'))}}>销毁组件</button>

react性能优化 shouldComponentUpdate()

// 父组件定义一个切换选中框的一个方法
handlerChange = (id) => {
    this.state.list.forEach(item=>{
        if(item.id === id){
          item.flag = !item.flag
        }
    })

    this.setState({
        list: this.state.list
    })
}
// 将切换的方法传递给子组件
render() {
	let {list} = this.state
    return (
        <ul>
            {
                list.map(item => {
                // 这里的flag状态需要单独传递
                return <Two key={item.id} flag={item.flag} item={item} handlerChange={this.handlerChange} />
                })
            }
        </ul>
    )
}
// 子组件 Two
change (id) {
    this.props.handlerChange(id)
}
//询问组件是否进行更新操作,如果不写默认返回true,就代表组件要更新了,就会走render
//如果说这个钩子函数返回false,就代表不让组件更新,就不会走render
shouldComponentUpdate(nextProps, nextState) {
// nextProps: 拿到最新的状态
// 这里需要拿到最新的状态和之前的状态比较, 如果不是一样的, 那么就执行render函数, 这样render函数就只会被触发一次
// 但是flag状态不可以在这里通过对象的方式取出来, 因为在这里拿出来的对象是跟nextProps.item是一个引用地址,会拿到最新的状态,这样就永远返回的式false, 代表render不会执行						   
   if(this.props.flag !== nextProps.item.flag){
       return true
   }
   return false
}

render() {
	// 这里每次切换选中框render都会执行三次, 所以性能损耗太大
    console.log('two-render')
    let {item} = this.props
    return (
        <li>
            {item.text} 
            <input onChange={this.change.bind(this, item.id)} 				 			type='checkbox' checked={item.flag} />
        </li>
    )
}

再者, 也可以将使用 PureComponent 纯组件的方式, 原理使用了上面的方式

import React, { Component,PureComponent } from 'react'
export default class index extends Component {
	...
}

为什么要优化?

因为每次调用 this.setState() 方法修改状态, 那么想要视图更新, 那么必定要走render(), 即使这个状态已经修改成你想要的状态了, 当你再点击也会走render函数, 所以会损耗一定的性能

constructor () {
    super()
    this.state = {
    	num: 10
    }
}
// 2.当使用这个方法之后, 视图修改成为1000后, 再点击按钮. render是不会执行的, 当然使用纯组件也是可以的
shouldComponentUpdate(nextProps, nextState){
	return nextState.num !== this.state.num
}
render() {
	// 1.即使视图已经是1000了, 当你再点击按钮, 还是会执行render
    console.log('two-render')
    let {item} = this.props
    return (
        <li>
            {this.state.num}
            <button onClick={()=>{this.setState({num: 1000})}}>改变num</button>
        </li>
    )
}

如果是值比较的话,外部传入的属性与内部的属性不一样的话,才会进行render更新操作

如果是引用地址比较的话,例如数组,之前的属性数组的地址与更改后的新的数组的地址一致,那么就不会进行render的更新操作

export default class index extends PureComponent {
    addArr = () => {
    	// 使用push()方法因为还是之前的地址, 所以render不会执行, 视图就不会更新
        // this.state.app.push(3)
        this.setState({
            arr: [...this.state.arr, 3] // 这样因为每次一个新的引用地址
        })
    }
    
    render() {
        return (
            <li>
                {this.state.arr}
                <button onClick={this.addArr}>改变arr</button>
            </li>
        )
    }
}

总结:

所谓的性能提升就是说白了,就是在某些场景下可以优化react的render的渲染次数。
1.在普通组件里面可以使用shouldComponentUpdate钩子函数提升react性能。 (nextProps,nextState)
在内部可以判断组件外部接受的最新属性与之前的属性是否一致,从而约束render刷新的时机。
只要结果返回true,render就会立马执行渲染更新,返回false就代表render不会执行。

2.可以使用PureComponent来优化性能。内部机制是通过浅比较去实现的。
3.对于无状态组件的话,使用 React.memo(组件) 来进行性能提升 

forceUpdate() 强制更新

vue里面类似的方法 this. f o r c e U p d a t e ( ) o r t h i s . forceUpdate() or this. forceUpdate()orthis.set()

constructor () {
    super()
    this.state = {
    	a: 10
    }
    this.b = 50
}
componentDidMount () {
    // this.state.a = 20 // 不推荐使用
    this.setState({
    	a: 20
    })

    // 这里修改this上面绑定的属性, 如果想要视图也修改, 那么就可以使用强制更新
    this.b = 1000

    // 强制更新
   	this.forceUpdate()
}

componentDidUpdate() 数据改变引发真实dom结构挂载完毕才会触发这个函数

如果出现轮播图实例化划不动的问题, 可能是在真实dom结构还没加载完毕已经实例化轮播图了, 这样componentDidUpdate()可以解决这个问题

componentDidUpdate(){
	// 因为数据改变这个函数可能会被执行多次, 所以防止实例化多次, 加一个判断
    if(!this.swiper){
        this.swiper = new Swiper ('.swiper-container', {  
            loop: true, // 循环模式选项 
            // 如果需要分页器
            pagination: {
            el: '.swiper-pagination',
            },
        })   
    }
}

还可以通过将swiper 封装成一个组件, 在父组件请求数据, 将数据传递给swiper组件, 判断数据的长度是否存在来加载swiper组件

// 这里是将需要生成多份的swiper-slide封装出去
<div>
    {
    	this.state.list.length > 0 ? <Swiper list={this.state.list} /> : ''
    }
</div>

PropTypes限定传入的数据类型

// 向子组件传递两种数据, 一个是字符串, 一个是数组
<One num={'111'} userArr={this.state.userArr} />

// One组件 
import PropTypes from 'prop-types'
// 配置默认项num的值, 外部不给就用自己配置的, 外部传了就覆盖默认的
static defaultProps = {
	num: 100
}
static propTypes = {
    //限定外部传入的num属性可以是number/string/boolean类型的, 而且是必传项 isRequired
    num: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
    // 规定外部必须传递一个userArr的数组, 结构是一个对象
    // 对象包括里面包含两个字段分别是id: number.isRequired, sex:string.isRequired
    userArr: PropTypes.arrayOf(PropTypes.shape({
        id: PropTypes.number.isRequired,
        sex: PropTypes.string.isRequired
    })).isRequired
}

Context

import React, {Component, createContext} from 'react';
// Context 提供了一个无需为每层组件手动添加 props, 就能在组件树中传递数据的方法
let counterContext = createContext()
// Consumer 消费者, Provider 提供者
const {Consumer, Provider} = counterContext

// 定义一个提供者的组件, 在App组件里面包裹着其他组件, 这样里面的组件都可以使用提供者的数据了
class CounterProvider extends Component {
  constructor () {
    super()
    //所有的CounterProvider的后代组件都可以共享的状态
    this.state = {
      count: 100
    }
  }
  incrementCount = () =>{ 
    this.setState({
      count: this.state.count+1
    })
  }
  decrementCount = () =>{
    this.setState({
      count: this.state.count-1
    })
  }
  render () {
    return (
     //Provider需要写一个value属性,value就是给Consumer提供的数据或者方法
      <Provider value={{ 
        count: this.state.count,
        incrementCount: this.incrementCount,
        decrementCount: this.decrementCount
        }}>
        // 这里是为了能够显示 <CounterProvider />包裹的内容, 有点像vue的槽口
        {this.props.children}
      </Provider>
    )
  }
}

class Counter extends Component {
  //指定contextType 读取当前的counterContext
  //React就会向上去查找最近的Provider ,然后使用它的值
  //固定写法, 写上在this里面有对应的数据了
  static contextType = counterContext
  render () {
    // console.log(this)
    return (
      <div>
        {this.context.count}  // 100
      </div>
    )
  }
}

// 消费者组件(函数式组件)
function CounterBtn (props) {
  return (
    <Consumer>
      {
        //Consumer里面必须是一个函数,函数的参数就可以获取到Provider转递的值
        // 可以解构出CounterProvider组件提供的两个方法
        ({incrementCount, decrementCount})=>{
          const handler = props.type === 'increment' ? incrementCount : decrementCount
          return <button onClick={handler}> {props.children} </button>
        }
      }
    </Consumer>
  )
}

// App组件
class App extends Component {
  render () {
    return (
      <CounterProvider>  // 提供者组件
      	// 为了能够区分两个不一样的加减功能,传递了一个字符串, 好判断提供者里面的两个方法
        <CounterBtn type="increment">+</CounterBtn> 
        <Counter />
        <CounterBtn type="decrement">-</CounterBtn>
      </CounterProvider>
    )
  }
}

redux

// index.js  store文件入口 
import {createStore} from 'redux'
import reducer from './reducer'
//可以通过createStore()创建一个store,参数需要接受reducer
const store = createStore(reducer)
export default store

// state.js   // 初始化状态, 提供给action
export default {
  todos: [
    {id: 1, title: '今天周一', isFinished: false},
    {id: 2, title: '检查作业', isFinished: false}
  ]
}
// actionCreators.js  // 创建一个任务由store派发给action
import store from './index'
export default {
  addNewTodo (title) {
    let action = {
      type: 'addNewTodo',
      title
    }
    store.dispatch(action)
  },
  ...
}
// reducer.js  // 执行action 任务, 修改状态,状态一旦修改, 视图也将改变
import state from './state'
const reducer = (prevState=state,action) => {
  let new_state = {...prevState}
  switch(action.type){
    case 'addNewTodo':
      //为了让之前的数据引用地址跟修改之后返回的数据地址不一样, 如果返回的是一样的, 那么store会认为你没有修改过数据, 那么视图也不会变化
      new_state.todos = new_state.todos.slice() 
      new_state.todos.unshift({id:handler.getId(new_state.todos), title: 	action.title, isFinished: false})
      break;
      ...
   }
   return new_state
}
export default reducer

const handler = {
  getId(todos){
    todos = todos.slice()
    if(todos.length === 0) return 1;
    return todos.sort((a, b) => {
      return b.id - a.id
    })[0].id+1
  },
}

actionType来去规定action的类型为常量的文件, 来约束actionCreators和action 之间的标志信息的容错率

export const ADD_NEW_TODO = 'ADD_NEW_TODO'
...

reducer的拆分

// 需要在store根目录文件夹下新建reducer
import {combineReducers} from 'redux'
import todolist from './todolist/reducer'  // 分支
...
// 通过combineReducers来合并所有的 reducer 分支
const reducer = combineReducers({
  todolist  // 合并
})
export default reducer

// 但是后续使用的组件路径和调用方法就不一样了
// 因为改了文件, 所以store.getState()只能拿到合并分支的对象, {todolist: {todos: ...}}
console.log(store.getState())
this.state = {
	// todos: store.getState().todos
	todos: store.getState().todolist.todos // 获取到数据
}

react-redux

Prodiver 提供者 属性上通过store将数据派给容器组件
connect 用于连接容器组件与UI组件
connect(mapStateToProps,mapDispatchToProps)(UI组件)
容器组件内部帮你做了 store.subscribe() 的方法订阅状态变化 ==> 容器组件监听状态改变了 ==> 通过属性的方式传给UI组件
store.getState()的状态转化为展示组件的props使用

// index.js
import store from './store'
import {Provider} from 'react-redux'

//这边通过store属性传下去,那么所有的Provider的后代组件都可以通过connect连接,然后获取redux的状态了
<Provider store={store}>
	<App />
</Provider>

// 组件
import actionCreators from '../../store/todolist/actionCreators'
import {connect} from 'react-redux'

// 转化为展示组件props的属性和方法
// mapstate这里拿到的是合并后分支的对象
export default connect(mapstate => mapstate.todolist, actionCreators)(TodoContent)

redux-thunk中间件

通常情况下,action只是一个对象,不能包含异步操作,这导致了很多创建action的逻辑只能写在组件中,代码量较多也不便于复用,同时对该部分代码测试的时候也比较困难,组件的业务逻辑也不清晰,使用中间件了之后,可以通过actionCreator异步编写action,这样代码就会拆分到actionCreator中,可维护性大大提高,可以方便于测试、复用,同时actionCreator还集成了异步操作中不同的action派发机制,减少编码过程中的代码量
redux-thunk原理:
可以看出来redux-thunk最重要的思想,就是可以接受一个返回函数的action creator。如果这个action creator 返回的是一个函数,就执行它,如果不是,就按照原来的next(action)执行。
正因为这个action creator可以返回一个函数,那么就可以在这个函数中执行一些异步的操作。换言之,redux的中间件都是对store.dispatch()的增强

// store/index.js 在redux 里面解构出applyMiddleware
import {createStore, applyMiddleware} from 'redux'
import thunk from 'redux-thunk'
const store = createStore(reducer, applyMiddleware(thunk))

// actionCreators.js 
compute(){
    return dispatch => {  // 将dispatch派发任务的方法当做参数传递出来
        setTimeout(() =>{   // 函数内部可以实现异步操作 
            let action = {
                type: COMPUTE
            }
            dispatch(action)  // 派发任务
        }, 1000)
    }
}

高阶组件

高级函数: 一个函数内部返回一个新的函数, 内部采用了闭包的写法

var add = x => {
	return y => {
		return x+y
	}
}
var add = x => y => {x+y} // 简写

高阶组件:(HOC) Higher-Order-Component
高阶组件本质上就是一个函数,内部可以接收一个组件,然后返回新的组件。
例如: React.memo(UI组件) connect()(UI组件)

// About组件
import React, { Component } from 'react'
import WithCopy from './withCopy'

class About extends Component {
  render() {
    return (
      <div>
        about {this.props.data}
      </div>
    )
  }
}
export default WithCopy(About)

// WithCopy高阶组件
import React, {Component} from 'react'
const WithCopy = Comp => {
  // 内部返回一个组件, 相当于页面渲染的是这个组件, 只有把About组件当做参数在这里被渲染
  return class WithCopyRight extends Component {  
    render () {
      return (
        <div>
     	 //App.js向About传递的数据被WithCopy高阶组件劫持了,必须向下传递About才能拿到数据
          <Comp {...this.props} /> 
          &copy;b站大佬
        </div>
      )
    }
  }
}
export default WithCopy

// App.js
import About from './components/about'
<About data='哈哈哈' />

后续如果还有一些高阶组件使用, 会在调用()的时候出现层层嵌套的现象, 那么可以使用修饰符

对于CRA的定制

采用装饰器的写法实现调用高阶组件

@WithCopy
class About extends Component {
    render() {
        return (
            <div>
                about
            </div>
        )
    }
}
export default About

很不幸,目前的cra是不支持这种写法,我们需要单独进行定制,让其支持这种写法

  1. yarn add react-app-rewired
  2. 对脚手架轻微的调整
   "scripts": {
   -   "start": "react-scripts start",
   +   "start": "react-app-rewired start",
   -   "build": "react-scripts build",
   +   "build": "react-app-rewired build",
   -   "test": "react-scripts test --env=jsdom",
   +   "test": "react-app-rewired test --env=jsdom",
       "eject": "react-scripts eject"
   }
  1. yarn add customize-cra
  2. 在src同级目录下新建文件 config-overrides.js, 里面配置如下
   const {
     addDecoratorsLegacy,
     override
   } = require("customize-cra");
   
   module.exports = override(
     addDecoratorsLegacy()   //就代表让当前的cra支持decorators了!
   )

其实就会发现 react面向社区化的。它把一些常用到的包都放在社区,用到的话单独去查找去进行相应的配置。
vue的话面向官方化,很多东西内部都帮助我们去实现了。 vue.config.js文件标准

React-Router

最新的路由的版本是5.1.2的版本。里面的话提供了一些包
所在在做web端开发的时候只需要安装react-router-dom就可以了,因为内部已经包含了最核心的内容了

// index.js  as 就是起别名
import {HashRouter as Router} from 'react-router-dom'
ReactDOM.render(
  <Router>
    <App />
  </Router>,
  document.getElementById('root')
);

// render函数内部
<Switch>
	// routeProps将component指明的路由组件属性和方法传递给Home组件, 因为Home组件只是一个普通的类组件
    <Route path="/home" render={(routeProps)=> {
    	return this.state.isShow ?  <Home data="666" {...routeProps} /> : ''
    }} />
    <Route path="/article" component={Article} exact />
    <Route path="/article/:id" component={ArticleDetail} />
    <Route path="/404" component={NotFound} />
    <Redirect from='/' to='/home' exact />  
    <Redirect to='404' />
</Switch>

// Article.js
import {NavLink as Link} from 'react-router-dom'
<div>
    <p>Article文章列表</p>
    <span><Link to='/article/1?title=文章一'>文章一</Link></span>
    <span><Link to={{
        pathname: '/article/2',
        state: {title: '文章二'}
        }}>文章二</Link>
    </span>
</div>

// ArticleDeatil.js
// this.props.match.id 获取动态id 
// this.props.location.search 获取到query参数
// this.props.location.state 获取到隐式参数
console.log(this.props)

Route里面的path代表路径,component代表映射的路由组件

Redirect 重定向 from 从哪里来 to 指明目标到哪里去

Link 声明式跳转, 默认会被渲染成a标签

NavLink 在被点击的哪一项会被添加一个active类名

exact 声明路径为完全匹配

Switch包裹的路由只会匹配一个

如果 没有加上exact 那么路径里面只要带有 / 就会重定向到home页

如果 没有加上exact 那么详情页的渲染就会被阻止掉

render={()=> { 组件 }} 的好处式, 可以根据条件渲染, 还可以给组件传递参数

后续BackHome 返回home的组件要实现路由跳转, 就要用 {withRouter} 映射成路由组件, 因为只有被component指明的组件this.props才会有路由相关的属性和方法

import {withRouter} from 'react-router-dom'
class BackHome extends Component {
  backHome = ()=>{
    // this.props.history.push('/home')
    // Home因为在App组件通过参数 routeProps 把路由相关的属性和方法传递给Home组件了,所以Home组件也可以this.props.location.state拿到数据
    this.props.history.push({
      pathname: '/home',
      state: {
        id: '哈哈哈'
      }
    })
  }
  render() {
    console.log(this.props)
    return (
      <div>
        <button onClick={this.backHome}>返回首页</button>
      </div>
    )
  }
}

export default withRouter(BackHome)

上面所述, 就是这几天react的小白纪录…
后续还有react实战项目搭配Antd组件库纪录…
谢谢大家的支持
kita…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值