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>
)
}
上面例子可以看到父组件虽然更新了,但子组件没有重新render
但注意的是 有时候useCallback 也会有可能触发无限循环。 用ruseReducer(解决)
useMemo
跟useCallback 差不多,但是useCallback 缓存函数, useMemo 缓存值, 有点像 vue
的 computed
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";
测试一下;
redux, mobx
余着