HOOK
react 16.8+
函数式组件的性能会更好,函数式组件没有this指向的问题,react在16.8以后推出了hook,解决了函数式组件无state和生命周期,ref的问题
render
使用高阶函数会使得组件有很多层的多余嵌套,HOOK中的render可以解决这个问题:
App.js
<Wrap render={()=>{
return (
<mark>test</mark>
)
}}>
Wrap组件
render(){
return this.props.render(); //其实这里的render写别的单词都无所谓,只是一般习惯使用render
}
useState
userState:用来创建state值的hook
userStare调用接收的参数为初始值,参数类型可以为直接写数据也可以是函数,函数返回值就是初始化的变量
const [map,setMap]=(()=>{
...
return tmp
})
返回值是一个数组,有两个值,第一个值代表的是初始化的变量,第二个代表的是修改state的方法
const [a,setA]=useState('aaa');
只要调用了修改了state的方法,组件函数就会重新执行,但是state不会重新赋值
第一次定义都开一块新的存空间,之后再对他们赋值就直接使用之前的内存空间
注意:
如果有多次调用useState无论任何情况,每一个组件函数执行useState赋值给变量顺序都应该是一样的
有判断语句,循环判断,里面的判断语句千万不能写useState语句,本质上是为了每一次组件甘薯运行,创建的state个数都是一样的
useEffect
useEffect:接收的第一个参数是一个函数,第二个参数是这个函数的依赖列表
(对count进行了操作,就属于它其中的依赖列表,只有第一个参数的函数,首次渲染执行,已经依赖发生变化时执行,没有发生变化,不执行)
该函数在组件第一次render之后执行,
等价于componentDidMount componentDidUpdata
该函数可以有返回值,返回值需要是一个函数
上一次useEffect return 的函数,在下一次useEffect执行前先执行。dom是已经更新的
可以相互抵消的
useEffect(()=>{
//render end之后执行
return ()=>{
console.log('return函数执行...')
}
},[count]);
后面的参数写空 [] 就可以实现 componentDidMount 的效果 只有在挂载完成后执行
useEffect(()=>{
console.log('componentDidMount.....');
},[]);
useEffect(()=>()=>{
console.log('componentWillUnMount....');
},[])
两个可以一般用来执行监听可移除监听,这样的功能最好可以写在一起
const isRun = useRef(); 是固定的值,以后更新都是之前的 内存空间
//const isRun = {current:false} 每次更新都会重新赋值为false,起不了作用
useEffect(()=>{
console.log(isRun);
if(!isRun.current){
isRun.current=true;
return;
}
console.log('componentDidUpdata...');
})
ref
可以当成属性来使用,再次render的时候不会分配新的地址
函数式组件命名:
const Two=()=>{
return(
)
};
ref拿到组价是为了拿到组件的state值和他的方法,
函数式组件没有this所以在函数式组件上设置ref是没有用的,
但是转发是可以转发的
memo (react提供的一个方法,不属于HOOK)
memo相当于pureComponent
会浅比较
const Header=memo((props)=>{ 只能阻挡props,不能阻挡state
console.log('header render...');
...
},(oldProps,newProps)=>{ //true 不渲染 false渲染
})
useMemo (属于HOOK)
useMemo是渲染前执行
useEffect是渲染后执行
跟useEffect一样,需要接受两个参数,参数1是可执行函数,有返回值。参数2是依赖的数据。(可以是表达式,但是必须使用到了state值)
依赖的数据发生变化 ,useMemo 的函数就执行
useMemo和useEffect差别:
useMemo render前就会计算得到结果,useEffectrender后执行函数
useMemo和计算属性很像
let result=useMemo(()=>{
return title.length;
},[title]);
useCallback
<Header title={title} onChange={(val)=>{
console.log(1); // 即使使用了memo每次组件更新,这里的函数都会是一个新的函数,所以还是会更新,
//直接将这个方法放到外面去很多时候是不合适的(会使用到state值等)
}}>
</Header>
解决方法:
let result = useMemo(()=>{
return(val)=>{
setTitle('hi');
}
})
useMemo依赖发生变化,执行函数,得到新的结果,依赖不变还是原来的结果
useCallback依赖发生变化,直接得到新的函数, 依赖不变,还是原来的函数
上面的写法和下面的写法完全等价
let cb= useCallback((val)=>{
setTitle('h1');
},[]);
组件必须使用useCallback优化事件,但是是不同标签就没有必要
useContext
One.contextType=DataContext; //不能写多个
要使用多个context 只能使用Consumer 进行嵌套 ,很简单的项目会使得看起来很复杂
<TitleContext.Consumer>
{
({title})=>{
return <DataContext.Consumer>
{
({color})=>{
return <h2 style={{color: color}}>{title}</h2>
}
}
</DataContext.Consumer>
}
}
</TitleContext.Consumer>
这样的写法很麻烦 使用:useContext 就可以完美解决
import DataContext from '../context/DataContext'
import TitleContext from '../context/TitleContext'
export default ()=>{
const {color} = useContext(DataContext);
const {title} = useContext(TitleContext);
return (
<div>
<h1>two组件</h1>
<p style={{color: color}}>
{title}
</p>
</div>
)
}
自定义HOOK
自定义的hooks是一个函数,需要以use开头
使用的时候是调用函数的形式,可以传参可以有返回值
自定义的hooks跟普通函数的差别,在于可以使用react提供的hooks(useContext,useState …)
组件之间传值就可以利用自定义的hook
export const useColor = ()=>{
const color = useContext(StyleContext);
useEffect(() => {
console.log('执行了');
}, [color]); //color 变了再去执行代码
return color;
}
import {useColor} from '../plugin/style'
let color = useColor();
就可以在组件中直接进行使用
规则:
1.最顶层或者自定义hook会报错
2.使用了依赖但是没有定义依赖,会报警告
非受控组件设置默认值:
defaultValue
使用reduex的项目结构:
按模块划分的redux架构
src
components
utils
config
pages(依赖actions)
user
goods
cart
store
index.js(依赖reudcer)
reudcer(依赖types)
user(initialState, reducer)
goods
cart
index
actions(依赖types)
user(action, action, action)
goods(action, action, action)
cart(action, action, action)
types
user
goods
cart
按功能划分的
src
components
utils
config
pages
user
component(依赖actions)
reducer(依赖types)
actions(依赖types)
types
goods
component
reducer
actions
types
cart
component
reducer
actions
types
store
index.js(依赖每一个功能的reducer)
duck模式
src
components
utils
config
pages
user
component(依赖actions)
reuder(types initialState actions<export> reducer<export default>)
goods
component
reducer(types initialState actions reducer)
cart
component
reducer(types initialState actions reducer)
store
index.js(依赖每一个功能的reducer)
types
在store文件起始位置定义好action的type ,方便后面的迭代
selector
在select中进行计算,提高代码的耦合
reselect插件 提升性能
reselect中的createSelector接收两个参数
参数1:有selector函数组成的数组,参数2:就是select函数
作用:参数1中的所有selector函数返回值其中一个变化了,参数2的selector就需要重新执行,返回结果作为createSelector的返回结果,并且缓存下来。参数1中的所有selector函数返回值都没有变化,那么就不需要重新计算,使用上一次的缓存结果。
弊端:如果组件有复用,给的createSelect应该是独立的一个值,
export const showListSelector = createSelector(
[statusSelector, listSelector],
(status, list) => {
console.log('计算..showList');
let showList = status === 'all' ? list : list.filter(item=>item.status===status);
return showList;
}
)
react-redux 7.0+(支持hook)
react-redux 7.0+支持hook,不再需要connect高阶函数
需要state 就引入useSelect ,需要派发事件需要引入useDispatch
useSelect
useSelector(selector, shallowEqual);//默认是===判断是否变化了,如果需要浅比较,再使用shallowEqual
使用默认判断写法:
const status = useTest(statusSelector);
useDispatch
import { useDispatch } from 'react-redux'
let {item} = props;
import { finishAction } from '../store'
const dispatch=useDispatch();
const finshClickAction=useCallback((id)=>{
dispatch(finishAction(item.id));
},[dispatch,item.id]);
<button onClick={finishClickAction}>完成</button> //在函数式组件中没有this指向的问题
useShallowEqualselector 深比较函数
路由使用HOOK
使用react-router 中的react-router-config 可以实现类似vue的路由配置效果
跟vue的区别在于,配置子路由的时候子路由的path必须写完整
子路由需要写在父组件的对应位置(同vue)
import {renderRoutes} from 'react-router-config'
{renderRoutes(routes)} //在app.js下
{renderRoutes(props.route.children)} // 在需要子路由的父组件下
路由守卫
1:判断IsLogin状态,false就重定向到登录页,true就渲染组件
2.1方法要拦截多个页面就要在组件中重复操作上面的操作,可以使用自定义hook来封装
useAuth.js
// 鉴权
import React from 'react'
import {Redirect} from 'react-router-dom'
import {useSelector} from 'react-redux'
export default function useAuth(DOM){
let isLogin = useSelector((state)=>state.isLogin);
return isLogin ? DOM : <Redirect to='/login'/>;
}
3.修改react-router-config的源码
import React from "react";
import { Switch, Route, Redirect } from "react-router";
export function renderRoutes(routes, extraProps = {}, switchProps = {}, isLogin) {
return routes ? (
<Switch {...switchProps}>
{routes.map((route, i) => (
<Route
key={route.key || i}
path={route.path}
exact={route.exact}
strict={route.strict}
render={props =>{
//需不需要鉴权
if(route.isAuth){
//需要
if(isLogin){
//登录了
return route.render ? (
route.render({ ...props, ...extraProps, route: route })
) : (
<route.component {...props} {...extraProps} route={route} />
)
}else{
//没有登录
return <Redirect to="/login"/>
}
}else{
//不需要
return route.render ? (
route.render({ ...props, ...extraProps, route: route })
) : (
<route.component {...props} {...extraProps} route={route} />
)
}
}}
/>
))}
</Switch>
) : null;
}
routes.js
const routes=[
{
path: '/three',
component: lazy(()=>import('../pages/Three')),
isAuth: true
},
...
]
withRouter (无router对象组件可以用有这些路由对象)
作用:把不是通过路由切换过来的组件中,将react-router 的 history、location、match 三个对象传入props对象上
比如app.js
import {withRouter} from 'react-router'
....
export default withRouter(App);
在其他组件不建议使用它,比如如果这个时候需要使用到redux的高阶组件connect,
就需要相互嵌套,注意他们的嵌套顺序
const MyBox = withRouter(connect(()=>({}), ()=>({}))(Box));
作为子组件需要使用到 history、location、match,就在父组件上传过去即可使用
<MyBox history={props.history}/>
路由hooks
useHistory useParams useLocation useRouteMatch
import {useHistory} from "react-router-dom"
const history = useHistory();
history.push({
pathname:'/one/detail/1',
state:{
title:'hello hooks'
}
})
import {useParams,useLocation,useRouteMatch} from 'react-router-dom'
let params=useParams(); //获取到路由正向传递过来的值
let localtion=useLocation();