React 创建组件的方式

  • React.creactClass (已弃用)
  • Class 的方式创建组件
  • 纯函数的方式创建组件,即我们常说的Function Component

这里主要介绍Class 跟 Function Component 两种方式

Class

class 是有生命周期的概念的

  • 挂载的时候的生命周期: constructor -> render -> componentDidMount
  • 更新时的生命周期: getDerivedStateFromProps -> shouldComponentUpdate -> render -> componentDidUpdate
import React, { Component, PureComponent } from 'react';
Class Home extends Component {
     constructor(props) {
        super(props);
        this.state = {
            count: 1
        }
    }

    /**
     * 一般我们这里会做数据请求
     */
    componentDidMount() {}

    componentDidUpdate() {}

    /**
     * 
     * 这里一般我们会做一些优化, 比如减少重复渲染。
     * 父组件的state修改的话,会引起子组件的重新render, 但是当前子组件并没有依赖这个state,就可以用这个函数判断了
     * 要是优化的话 可以使用 PureComponent , 她会会我们的props 进行浅比较
     * @return boolean
     */
    shouldComponentUpdate(nextProps, nextState) {}

    /**
     * 这个生命周期时组件准备卸载了
     * 这里可以对一些定时器,订阅等等做取消  
     */
    componentWillUnmount() {}

    /**
     * 这个方法无论是props 更新或者state更新都会进入到这个静态函数
     * 返回一个对象, 当前的对象就是state,
     * 如果返回null,那不对state做任何操作
     */
    static getDerivedStateFromProps(nextProps, prevState) {}

    add = () => {
        // 要注意的是 这个setState 修改的state 并不是立刻修改的,
        this.setState({
            count: this.state.count + 1
        })
    }

    render() {
        return (
            <>
                <div>state: {this.state.count}</div>
                <div onClick={this.add}>+</div>

                <div>props: {this.props.value}</div>
            </>
        )
    }
}

注意: 在class 组件下 只能够用setState 去修改state的数据, 它不像vue 那样可以直接修改数据。

setState

setState 有两个参数, 一般我们会这么写

// 一般我们会这么写
this.setState({
    str: '123'
})

// 但实际上callback 还有另一个参数, callback
this.setState({
    str: '123'
}, () => {
    console.log("callback")
}}

// 另外我们setState 也可以用方法
this.setState((prevState) => ({
    count: prevState.count + 1
}))

props

react 的 props 跟 vue 的 props 的传递方式大致一样,也是从父组件传props

<Home value="hello world">

高级用法

HOC

HOC 就是一个高阶组件, 简单说就是传入一个组件, 返回一个组件的函数。它可以做到以下几点

  • 劫持渲染
  • 劫持props
// 比如一个简单的loading组件, 当你要为你已经写过的组件增加一个loading状态的时候,此时可以用HOC
const HOC = (Component) => {
    return class extends Component {
         render() {
            const { loading, ...rest } = this.props;

            if(!loading) return "loading...";
            return <WrappedComponent {...rest} />
        }
    }
}

// 使用
const EnhanceHoc = HOC(Home);

render Props

用的比较多的一种方式是Mdoal 框 或者 Drawer , 虽然有很多UI 框架帮我们封装好Modal框,但有时候还要写visible, 关闭方法等等,就有点烦,这时候就可以用render Props

const CustomModal = ({visible, cancel}) => {
    return (
        <Modal visible={visible} onCancel={cancel} >
            Hello World
        </Modal>
    )
}
// modalContainer实现
class ModalContainer extends Component {
    state = {
        visible: false
    }

    handleCancel = () => {
        this.setState({
            visible: false
        })
    }

    show = () => {
        this.setState({
            visible: true
        })
    }

    render() {
        const { visible } = this.state;
        const { children } = this.props;
        return (
            children({
                visible: visible,
                show: this.show,
                cancel: this.handleCancel
            })
        )
    }
}
//使用
class App extends Component {
    render() {
        return (
            <div>
                <ModalContainer>
                    ({visible, show, cancel}) => (
                        <>
                            <CustomModal visible={visible} cancel={cancel}></CustomModal>
                            <Button type="primary" onClick={show}>
                                Click
                            </Button>
                        </>
                    )
                </ModalContainer>
            </div>
        )
    }
}

react Hooks

useState

简单说 类似class 组件上面的 this.setState

const Home = () => {
    // useState 的参数可以是简单类型, 也可以是方法
    const [count, setCount] = useState(0);
    const [toggle, setToggle] = useState(false);
    const increment = () => {
        setCount(count++);
    }
    const handleToggle = () => {
        // setToggled 参数也可以用function
        // setToggle((prevState) => nextState);
        setToggled(toggled => !toggled);
    }

    return (
        <>
            <button onClick={increment}></button>
            <div>{count}</div>
            <div onClick={handleToggle}>{toggle}</div>
        </>
    )
}

useEffect

在Function Component 中, 是没有 生命周期的概念的,那么请求数据的时候就用到了useEffect,
但是useEffect 其实包含3个生命周期 componentDidMount, componentDidUpdate, componentWillUnmount
注意: 不能在 if/else 或者是for循环中使用useEffect

const Home = () => {
    const [loading, setLoading] = useState(false);


    /**
     * useEffect 提供两个参数, 一个是回调函数, 一个是依赖数组,
     * 当第二参数不填的时候, 一旦state变化的时候, 就会执行这个函数
     * 当第二个参数为 [] 空数组时, 就等同于 class组件的 componentDidMount
     * 当第二个参数为[loading], 代表只有loading 发生变化是,回调函数才会执行
    */

    useEffect(() => {
        // 函数执行

       return () => {}
    }, [])

    return (
        <>
            <button onClick={increment}></button>
            <div>{count}</div>
            <div onClick={handleToggle}>{toggle}</div>
        </>
    )
}

useCallBack

useCallback 简单来说就是对我们的方法,进行缓存,达到一个性能优化的效果
用的最多场景的是从父组件传方法给子组件,这时就可以用useCallback 了

子组件onChange调用了父组件的handleOnChange
父组件handleOnChange内部会执行setText(e.target.value)引起父组件更新
父组件更新会得到新的handleOnChange,传递给子组件,对于子组件来说接收到一个新的props
子组件进行不必要更新

const Child = React.memo((props) => {
  console.log(props);

  return (
    <div>
      <input type="text" onChange={props.onChange}/>
    </div>
  )
})

const Parent = () => {
  const [count, setCount] = useState(0)
  const [text, setText] = useState('')

  const handleOnChange = useCallback((e) => {
    setText(e.target.value)
  },[])

  return (
    <div>
      <div>count: {count}</div>
      <div>text: {text}</div>
      <button onClick={() => setCount(count + 1)}>+1</button>
      <Child onChange={handleOnChange} />
    </div>
  )
}

demo

上面例子可以看到父组件虽然更新了,但子组件没有重新render

但注意的是 有时候useCallback 也会有可能触发无限循环。 用ruseReducer(解决)

useMemo

跟useCallback 差不多,但是useCallback 缓存函数, useMemo 缓存值, 有点像 vuecomputed

useRef

是一个一直会变的对象,他有一个.current 属性,可以保存dom, 可以是方法,等等

useReducer

这个其实跟redux 的reducer 有点相似,在定义的state之后,只能通过dispatch 触发对应的reducer事件(Actions)更新state
简单理解 :

       state -> UI -> 用户触发事件 执行action 
         ^                 |
         |                 |
         ------------------

看看官方的例子吧!

useContext

import React, { createContext, useContext, useState } from "react";

const MenuContext = createContext({
  index: 0
});

export default function App() {
  return (
    <div>
      <Menu defaultIndex={0}>
        <MenuItem index={0}>menu1</MenuItem>
        <MenuItem index={1}>menu2</MenuItem>
        <MenuItem index={2}> menu3</MenuItem>
      </Menu>
    </div>
  );
}

const Menu = (props) => {
  const { defaultIndex, onSelect } = props;
  const [currentActive, setActive] = useState(defaultIndex);
  const handleClick = (index) => {
    setActive(index);
    if (onSelect) {
      onSelect(index);
    }
  };
  const Icontext = {
    index: currentActive || 0,
    onSelect: handleClick
  };


  return (
    <ul>
      <MenuContext.Provider value={Icontext}>
        {props.children}
      </MenuContext.Provider>
    </ul>
  );
};

const MenuItem = (props) => {
  const { children, index } = props;
  const itemContext = useContext(MenuContext);
  const handleClick = () => {
    if (itemContext.onSelect) {
      itemContext.onSelect(index);
    }
  };
  return (
    <li onClick={handleClick}>
      {itemContext.index === index ? <a>active </a> : null}

      {children}
    </li>
  );
};

测试一下

进阶 自动配置index

就是 React.Children.map ,以及React.cloneElement

import React, { createContext, useContext, useState } from "react";

const MenuContext = createContext({
  index: 0
});

export default function App() {
  return (
    <div>
      <Menu defaultIndex={0}>
        <MenuItem>menu1</MenuItem>
        <MenuItem>menu2</MenuItem>
        <MenuItem> menu3</MenuItem>
      </Menu>
    </div>
  );
}

const Menu = (props) => {
  const { defaultIndex, onSelect } = props;
  const [currentActive, setActive] = useState(defaultIndex);
  const handleClick = (index) => {
    setActive(index);
    if (onSelect) {
      onSelect(index);
    }
  };
  const Icontext = {
    index: currentActive || 0,
    onSelect: handleClick
  };

  const childrenRender = () => {
    return React.Children.map(props.children, (child, index) => {
      if (child.type.displayName === "MenuItem") {
        return React.cloneElement(child, { index: index });
      }
    });
  };

  return (
    <ul>
      <MenuContext.Provider value={Icontext}>
        {childrenRender()}
      </MenuContext.Provider>
    </ul>
  );
};

const MenuItem = (props) => {
  const { children, index } = props;
  const itemContext = useContext(MenuContext);
  const handleClick = () => {
    if (itemContext.onSelect) {
      itemContext.onSelect(index);
    }
  };
  return (
    <li onClick={handleClick}>
      {itemContext.index === index ? <a>active </a> : null}

      {children}
    </li>
  );
};

MenuItem.displayName = "MenuItem";

测试一下;

hooks学习

redux, mobx

余着