react基础知识

137 阅读9分钟

react基础知识

1、setState()只有在合成事件和钩子函数中是异步的,在原生事件、setTimeOut和setInterval中是同步的

2、事件回调函数注意绑定this的指向,常用的三种方法

1、构造函数中绑定this并覆盖:this.change = this.change.bind(this)

2、方法定义为箭头函数: change = () =>{}

3、事件中定义为箭头函数: onChange = {() =>this.change()}

3、getDerivedStateFromProps

getDerivedStateFromProps(prop, state)会在render方法之前调用,并且在初始挂载及后续更新时都会被调用,它应返回一个对象来更新state,如果返回null则不更新任何内容(每次渲染前都会触发)

4、getSnapshotBeforeUpdate

getSnapshotBeforeUpdate() 在最近⼀次渲染输出(提交到DOM 节点)之前调⽤。它使得组件能在发⽣更改之前从 DOM 中捕获⼀些信息(例如,滚动位置)。此⽣命周期的任何返回值将作为参数传递给componentDidUpdate()

5、context

context的基本使用方式(类似于vue中的provide/inject) // 传递数据的组件

export const {Provider,Consumer} = React.createContext("默认名称"); 
<Provider value="dark"> // 使用provider包裹,这样ContextProp组件或者他的下层(子/孙...)就可以使用该值
     <ContextProp />
</Provider>
// 传递多个值的方式
<Provider value={{name, store}}>
     <ContextProp />
</Provider>

// 使用数据的组件

import {Consumer} from "./home"; // 引入传递数据组件中的Consumer,包裹要用的地方
<Consumer>
   {(value ) =>
        <div>
            <p>孙组件。获取传递下来的值:{value}</p>
         </div>
    }
</Consumer>

6、composition(插槽)

react中的composition(类似于vue中的slot插槽)

不具名插槽的使用

// 父组件
 <Myslot> // 引入的子组件
     <div>要放在子组件中的内容</div>
</Myslot>
// 子组件,有多个插槽时this.props.children.$$typeof有值则为不具名插槽
<div>
    组件本身的内容。。。
    {this.props.children} // 插槽的内容,这样就可以展示父组件放在插槽的东西了
</div>

具名插槽的使用

// 父组件(传递一个对象)
 <div>
   <Myslot>
      {
         {
            btn: <div>具名插槽</div>
         }
      }
   </Myslot>
</div>
// 子组件
 render() {
        console.log(this.props.children) // 将这个打印出来就知道接受到的是什么了,多个的时候这是一个数组
        return (
            <div>
                组件本身的内容
                {this.props.children[1].btn} // 插槽的内容,有多个插槽内容时候的写法
                 {this.props.children[1].btn} // 单个带名字的插槽的写法
            </div>
        );
    }

7、高阶组件HOC

高阶组件HOC: 高阶组件是一个工厂函数,它接收一个组件并返回另一个组件 作用:为了提高组件复用率,可测试性,就要保证组件功能单一性,但是若要满足复杂需求就要扩展单一的组件,因此就有了HOC

基础用法

const foo = Cmp =>{
            return props => { // 这个props就是组件接受到传递过来的props
                console.log(props) 
                return (
                    <div style={{color: 'red'}}>
                        <Cmp {...props}></Cmp>
                    </div>
                )
            }
        }
// 以上代码等同于(将return取消没写)
const foo = Cmp => props =>
     <div style={{color: 'red'}}>
         <Cmp {...props}></Cmp>
     </div>

调用

function Child() {
            return <div>12312312</div>
        }
        const foo = Cmp =>{
            return props => {
                console.log(props)
                return (
                    <div style={{color: 'red'}}>
                        <Cmp {...props}></Cmp>
                    </div>
                )
            }
        }

        const Foo = foo(Child) // 返回的就是一个组件
            return ( // render中调用
            <div>
                <Foo name={'zs'}></Foo>
            </div>
        );

HOC最终返回的是一个新的组件,可以把HOC的结果再当成参数传递给下一个HOC,形成链式调用

8、hooks的基本使用

hooks只能用于函数组件中 1、使你再无需修改组件结构的情况下复用状态逻辑
2、可将组件中相互关联的部分拆分成更小的函数,复杂组件将变得更容易理解
3、更简洁,更易理解的代码

使用

import React, { useState, useEffect } from 'react';
function Example() {
    // 声明一个叫 “count” 的 state 变量。将该值默认设置为1,只能通过setCount()来修改
    // 该方法里面传要修改的最终结果值
    const [count, setCount] = useState(1);
    useEffect(() => { // 类似于生命后期函数
        const number = setInterval(() => {
            setCount(count + 1)
        }, 1000)
        return () =>clearInterval(number) // 组件销毁时执行,清除定时器
    })
    return (
        <div>
            <p>You clicked {count} times</p>
            <button onClick={() => setCount(count + 1)}>
                Click me
            </button>
        </div>
    );
}

以下代码实现了一个列表的增加和删除

import React, {useState, useEffect} from 'react';

function Hooks(props) {
    const [fruits, setFruits] = useState(['apple', 'orange'])
    const [date, setDate] = useState(1);
    useEffect(() => {
        console.log('当date改变时候才会触发,因为useEffect第二个参数已经将其他的值改变过滤掉了')
        const time = setInterval(() => {
            setDate(date + 1)
        }, 1000)
        return () => clearInterval(time)
    }, [date]) // 当不想让其他值改变时触发useEffect,那么就将这个参数置为[]或者将想触发的值写在该数组中,如果没有这个参数,那么只要改变useState定义的值,都会触发useEffect
    return (
        <div>
            <FruiltsList fruits={fruits} onSetFruits={setFruits}></FruiltsList>
            <AddFruits fruits={fruits} onSetFruits={setFruits}></AddFruits>
            <p>{date}</p>
        </div>
    );
}

function FruiltsList({fruits, onSetFruits}) { // 函数组件此时就可以拿到props传递的值(包括传递的方法)
    function deleteFruit(index) {
        const temp = [...fruits]
        temp.splice(index, 1)
        onSetFruits(temp) // 修改fruits的值
    }
    return (
        <ul>
            {
                fruits.map((item, index) => {
                    return <li key={item+ index} onClick={() => deleteFruit(index)}>{item}</li>
                })
            }
        </ul>
    )
}

function AddFruits({fruits, onSetFruits}) {
    const [name, setName] = useState('')
    function addFruit() {
        onSetFruits([...fruits, name])
        setName('')
    }
    return (
        <div>
            <input type="text" value={name} onChange={(e) => setName(e.target.value)}/>
            <button onClick={() => addFruit()}>点击增加</button>
        </div>
    )
}

export default Hooks;

useEffect 给函数组件增加了执⾏副作⽤操作的能⼒。

概念:副作⽤(Side Effect)是指⼀个 function 做了和本身运算返回值⽆关的事,⽐如:修改了全局变量、修改了传⼊的参数、甚⾄是 console.log(),所以 ajax 操作,修改 dom 都是算作副作⽤
解决方法: 1、设置依赖

// 设置空数组意为没有依赖,则副作⽤操作仅执⾏⼀次
 useEffect(()=>{...}, [])

//如果副作⽤操作对某状态有依赖,务必添加依赖选项
useEffect(() => {
 ...
}, [count]);

2、清除工作:有一些副作用是需要清除的,清除工作可以防止内存泄漏

// 组件卸载后会执行返回的清理函数
useEffect(() => {
 const timer = setInterval(() => {
 console.log('msg'); 
 }, 1000);
 
 return function(){
 clearInterval(timer);
 }
 }, [])

9、useReducer的使用

作用:useReducer是useState的可选项,常用于组件有复杂状态逻辑时,类似于redux中reducer概念

import React, {useReducer, useEffect} from 'react'; // 引入

function UseReducer(props) {

    return (
        <div>
            <FruiltsList></FruiltsList>
        </div>
    );
}

function reducer(state, action) { // 定义传入的reducer函数,用于修改数据
    switch (action.type) {
        case 'delete':
            console.log(state)
            return []
        case 'add':
            return ['apple']
    }
}

function FruiltsList() { // 函数组件此时就可以拿到props传递的值(包括传递的方法)
    const [fruits, dispatch] = useReducer(reducer, ['apple' , 'orange']) // 调用useReducer并为fruits设置初始值
    return (
        <ul>
            {
                fruits.map((item, index) => {
                    return <li key={item+ index} onClick={() => dispatch({type: 'delete', payload: index})}>{item}</li>
                })
            }
        </ul>
    )
}

export default UseReducer;

10、useContext用于快速在函数组件中导入上下文

父组件

import React from 'react';
import ContextChild from './contextChild'
export const Context = React.createContext(''); // 定义Context
function UseContextPage(props) {
    return (
        <div>
            <Context.Provider value={{name: '我是父组件传递过来的值'}}> // 传值 -》和context一样
                <ContextChild></ContextChild>
            </Context.Provider>
        </div>
    );
}

export default UseContextPage;

接收数据的组件

import React, {useContext} from 'react';
import {Context} from './useContextPage' // 引入父组件中的context
function ContextChild(props) {
    const {name} = useContext(Context) // 此时即可拿到传递的值
    return (
       <div>
           <p>{name}</p>
       </div>
    );
}

export default ContextChild;

11、实现表单组件

import React, { Component } from "react";
import kFormCreate from "./kFormCreate";
const nameRules = { required: true, message: "please input your name!" };
const passwordRules = {
    required: true,
    message: "please input your password!",
};
class MyFormPage extends Component {
    handleSubmit = () => {
        const { getFieldValue } = this.props;
        const res = {
            name: getFieldValue("name"),
            password: getFieldValue("password"),
        };
        console.log("hah", res);
    };
    handleSubmit2 = () => {
        // 加⼊校验
        const { validateFields } = this.props;
        validateFields((err, values) => {
            if (err) {
                console.log("validateFields", err);
            } else {
                console.log("submit", values);
            }
        });
    };
    render() {
        const { getFieldDecorator } = this.props;
        return (
            <div>
                <h1>MyFormPage</h1>
                <div>
                    {getFieldDecorator("name", { rules: [nameRules] })(
                        <input type="text" />,
                    )}
                    {getFieldDecorator("password", [nameRules])(
                        <input type="password" />,
                    )}
                </div>
                <button onClick={this.handleSubmit2}>submit</button>
            </div>
        );
    }
}
export default kFormCreate(MyFormPage);

// 通过高阶组件形式完成数据的绑定和校验

import React, {Component} from "react";

export default function kFormCreate(Cmp) {
    return class extends Component {
        constructor(props) {
            super(props);
            this.options = {}; //各字段选项
            this.state = {}; //各字段值
        }
        handleChange = e => {
            let {name, value} = e.target;
            this.setState({[name]: value});
        };
        getFieldValue = field => {
            return this.state[field];
        };
        validateFields = callback => {
            const res = {...this.state};
            const err = [];
            for (let i in this.options) {
                if (res[i] === undefined) {
                    err.push({[i]: "error"});
                }
            }
            if (err.length > 0) {
                callback(err, res);
            } else {
                callback(undefined, res);
            }
        };
        getFieldDecorator = (field, option) => {
            this.options[field] = option;
            return InputCmp => (
                <div>
                    {// 由React.createElement⽣成的元素不能修改,需要克隆⼀份再扩展
                        React.cloneElement(InputCmp, {
                            name: field,
                            value: this.state[field] || "", //控件值
                            onChange: this.handleChange, //控件change事件处理
                        })}
                </div>
            );
        };

        render() {
            return (
                <div className="border">
                    <Cmp
                        {...this.props}
                        getFieldDecorator={this.getFieldDecorator}
                        getFieldValue={this.getFieldValue}
                        validateFields={this.validateFields}
                    />
                </div>
            );
        }
    };
}

12、实现弹窗类组件

// 1、通过Portal(传送门)实现 // 调用Dialog组件

import React, {Component} from 'react';
import {Button} from "antd";
import Dialog from "./dialog";
class Index extends Component {
    constructor(props) {
        super(props);
        this.state = {
            show: false
        }
    }
    changeShow =()=>{
        this.setState({
            show: !this.state.show
        })
    }
    render() {
        const {show } = this.state
        return (
            <div>
                <Button onClick={this.changeShow}>弹框显示</Button>
                {
                    show && <Dialog></Dialog>
                }
            </div>
        );
    }
}

export default Index;

// 具体实现

import React, {Component} from 'react';
import { createPortal } from "react-dom"; // 引入传送门
class Dialog extends Component {
    constructor(props) {
        super(props);
        const doc = window.document
        this.node = doc.createElement('div')
        this.node.setAttribute('class', 'name')
        doc.body.appendChild(this.node)
    }
    componentWillUnmount() { // 卸载时销毁
        const node = document.querySelectorAll('.name')
        console.log(node)
        for(const item of node) {
            window.document.body.removeChild(item);
        }
    }

    render() {
        return createPortal( // 在此处使用
            <div className={'dialog'} id={'dialog'}>
                1212312
            </div>,
            this.node,
        );
    }
}

export default Dialog;

13、树形组件

import React, {useState} from 'react';

function TreeNode(props) {
    const [expand, setExpand] = useState(false)
    const {title, children} = props.data
    function changeExpand() {
        setExpand(!expand)
    }
    return (
        <div>
            <div onClick={changeExpand}>
                <span>{children && children.length > 0 ? '+' : '-'}</span>
                <span>{title}</span>
            </div>
            {
                children && expand && children.length > 0 &&
                <div className="children">
                    {children.map(item => {
                        return <TreeNode key={item.key} data={item} />;
                    })}
                </div>
            }
        </div>
    );
}

export default TreeNode;

14、组件常见优化技术

shouldComponent PureComponent memo

shouldComponent 通过返回true/false来控制是否重新render

import React, {Component} from 'react';

class ShouldCom extends Component {
    constructor(props) {
        super(props);
        this.state = {
            count: 1
        }
    }
    componentDidMount() {
        setInterval(() => {
            this.setState({ // 在传递给子组件的值setState之后,不管该值是否实际发生了变化,都会触发子组件的render
                count: 2
            })
        }, 1000)
    }

    render() {
        const {count} = this.state
        return (
            <div>
                <Demo count={count}></Demo>
            </div>
        );
    }
}
class Demo extends Component{
    constructor(props) {
        super(props);
    }
    shouldComponentUpdate(nextProps, nextState, nextContext) {
        return this.props.count !== nextProps.count // 如果返回的是false则不再触发render。true则重新渲染页面
    }

    render() {
        console.log(this.props.count) // 没有使用shouldComponentUpdate就会一直触发render
        return (
            <div>
                {this.props.count}
            </div>
        )
    }
}
export default ShouldCom;

PureComponent 用于类组件,只能用于浅比较

import React, {Component, PureComponent} from 'react';

class PureCom extends Component {
    constructor(props) {
        super(props);
        this.state = {
            count: 1,
            obj: {
                a: 1
            }
        }
    }
    componentDidMount() {
        setInterval(() => {
            this.setState({ // 在传递给子组件的值setState之后,不管该值是否实际发生了变化,都会触发子组件的render
                count: 2,
                obj: {
                    a: 3
                }
            })
        }, 1000)
    }

    render() {
        const {count, obj} = this.state
        return (
            <div>
                <Demo count={count} obj={obj}></Demo>
            </div>
        );
    }
}
class Demo extends PureComponent{ // 使用PureComponent来创建组件则不会触发重复更新,但是只能用于浅比较,如果obj发生了变化,那么还是会触发render
    constructor(props) {
        super(props);
    }
    render() {
        console.log(this.props.count)
        return (
            <div>
                {this.props.count}
                {this.props.obj.a}
            </div>
        )
    }
}


export default PureCom;

memo 用于函数组件,也只能进行浅比较

import React, {Component, memo} from 'react';

class MomeCom extends Component {
    constructor(props) {
        super(props);
        this.state = {
            count: 1,
            obj: {
                a: 1
            }
        }
    }
    componentDidMount() {
        setInterval(() => {
            this.setState({ // 在传递给子组件的值setState之后,不管该值是否实际发生了变化,都会触发子组件的render
                count: 2
            })
        }, 1000)
    }

    render() {
        const {count} = this.state
        return (
            <div>
                <Demo count={count}></Demo>
            </div>
        );
    }
}

const Demo = memo((props) => { // 函数组件没有生命周期,props值改变就会一直触发更新,
    console.log(props)
    return (
        <div>{props.count}</div>
    )
})
export default MomeCom;

15、redux基本使用 npm install redux --save

1、通过createStore创建store
2、reducer初始化,修改状态函数
3、getState获取状态值
4、dispatch提交更新
5、subscribe变更订阅
store.js

import { createStore } from "redux"; // 引入createStore
function counterRedcuer(state = 0, action) { // 创建reducer
    switch (action.type) {
        case "add":
            return state + 1;
        case "minus":
            return state - 1;
        default:
            return state;
    }
}
const store = createStore(counterRedcuer); // 通过createStore(counterRedcuer)创建store
export default store;

reduxPage

import React, {Component} from 'react';
import store from "../../store/ReduxStore"; // 引入store
class ReduxPage extends Component {
    componentDidMount() {
        store.subscribe(() => { // 订阅
            this.forceUpdate() // 手动触发render
        })
    }

    render() {
        console.log('store', store)
        return (
            <div>
                <h1>reduxPage</h1>
                {store.getState()} // 获取定义的值
                // 调用reducer中定义的方法修改state
                <button onClick={() => store.dispatch({type: 'add'})}>+</button>
                <button onClick={() => store.dispatch({type: 'mines'})}>-</button>
            </div>
        );
    }
}

export default ReduxPage;

16、react-redux的基本使用 npm install react-redux --save

异步修改数据需要安装这两个依赖npm install redux-thunk redux-logger --save
index.js

import App from './App';
import store from "./store/reactReduxStore";
import {Provider} from 'react-redux';
// 用provider包裹并传入store
      <Provider store={store}>
          <App />
      </Provider>

store

import {createStore, applyMiddleware} from "redux"; // applyMiddleware为中间件,为了实现异步修改数据
import logger from 'redux-logger'
import thunk from 'redux-thunk'
const counterReducer = (state = 0, action) =>{
    switch (action.type) {
        case 'add':
            return state + 1
        case 'mines':
            return state - 1
        default:
            return state
    }
}
const store = createStore(counterReducer, applyMiddleware(logger, thunk))
export default store;

使用的地方reactReduxPage

import React, {Component} from 'react';
import {connect} from 'react-redux'

class ReactReduxPage extends Component {
    constructor(props) {
        super(props);
    }
    render() {
        console.log(this.props)
        const {num, add, mines, asyAdd} = this.props
        return (
            <div>
                <h1>reactReduxPage</h1>
                <span>{num}</span>
                <button onClick={() => add()}>+</button>
                <button onClick={() => mines()}>-</button>
                <button onClick={() => asyAdd()}>异步加</button>
            </div>
        );
    }
}
// 数据映射到props中
const mapStateToProps = state => {
    return {
        num: state
    }
}
// 方法映射到props中
const mapDispatchToProps = {
    add: () => {
        return {type: 'add'}
    },
    mines: () => {
        return {type: 'mines'}
    },
    asyAdd: () => dispatch => { // 异步修改数据
        setTimeout(() => {
            dispatch({type: 'add'})
        }, 1000)
    }
}
export default connect(mapStateToProps, mapDispatchToProps)(ReactReduxPage);

17、实现redux

export function createStore(reducer) {
    let currentValue = undefined // 返回值
    let currentListen = [] // 存放回调事件
    const getState = function () {
        return currentValue
    }
    const subscribe = function (listen) {
        currentListen.push(listen) // 将回调函数存起来
    }
    const dispatch = function (action) {
        currentValue = reducer(currentValue, action)
        currentListen.forEach(v => v()) // 执行存放的回调函数
    }
    dispatch({type: '执行switch中的default'}) 
    return {
        getState,
        subscribe,
        dispatch
    }
}