useEffect
- useEffect默认相当于DidMount和Didupdate生命周期
- useEffect第二个参数设置为[],则useEffect默认只执行一次,相当于DidMount
- 可以模拟Didupdate生命周期,第二个参数的数组里面设置需要监听的值的改变
- 返回应该函数,函数里面的内容相当于是WillUnMount生命周期,在里面执行一些,清除定时器,清除自定
特别注意
当useEffect第二个参数没有设置的时候,useEffect即能模拟DidMount,又能模拟Didupdate - 当页面的值改变的时候,相当于触发update,这时,还要执行,返回的函数的内容WillUnMount,这时,获取的值,是没有改变的值,这是由于闭包的特性,保留了上次的值
const TestFn = () => {
const [age, setAge] = useState(22)
useEffect(() => {
console.log('执行useEffect', age)
return () => {
console.log('模拟组件销毁', age)
}
})
const handleClick = () => {
setAge(age+3)
}
return (
<Fragment>
<div>{age}</div>
<button onClick={handleClick}>点击</button>
</Fragment>
)
}
export default TestFn
memo, useMemo, useCallback
- 主要用来做性能优化
- 当父组件的值发生变法时,子组件的也要重新渲染,造成不必要的性能浪费
- 操作方法,子组件用
memo
函数包裹返回,父组件去监听子组件有哪些值变法才去渲染子组件 - 传递的有函数用
useCallback
,传递的有值,用useMemo
,第二个参数是一个数组,看那些值有变化,才去执行,useCallback第二个参数默认是一个空数组 useCallback
主要是为了父组件传递给子组件时,传递了函数过去,默认传递了函数过去,使用了useMemo
和memo子组件还是会重新渲染,所以用useCallback
是为了让子组件不重新渲染,- memo默认是浅比较,所以当父组件传递引用类型(对象,数组)给子组件时,子组件还是会重新渲染,所有用
useMemo
的第二个参数判断哪些值,改变了才进行重新渲染
//默认是浅比较和React.# PureComponent一样
import React, { useState, memo, useMemo, useCallback } from 'react'
const Child = memo(({userInfo}) => {
console.log(99)
return (
<div>
Child
{userInfo.age}
</div>
)
})
const TestUseMemo = () => {
const [count, setCount] = useState(0)
const [age, setAge] = useState(1)
const onClick = () => {
setCount(count + 1)
}
const changeChildVal = () => {
setAge(age + 1)
}
const handleClick = useCallback(() => {
console.log('handleClick')
}, [])
const userInfo = useMemo(() => {
return {
age,
city: '成都'
}
}, [age])
return (
<div>
<div>{count}</div>
<div>{age}</div>
<button onClick={onClick}>点击</button>
<button onClick={changeChildVal}>点击2</button>
<Child handleClick={handleClick} userInfo={userInfo} />
</div>
)
}
export default TestUseMemo
hooks使用规范
- 只能用于react函数组件中和自定义hooks中,其他地方不可以
- 只能用于顶层代码,不能用于循环,判断中
- eslint-plugin-react-hooks
插槽
默认插槽
<MainFn testMainClick={testMainClick} >
<div>
我是默认slot
</div>
</MainFn>
//使用
const MainFn = ({children, testMainClick}) => {
const handleMain = () => {
testMainClick('AAA')
}
return (
<div>
<button onClick={handleMain}>MainFn</button>
<div>
{
children //默认插槽直接把children渲染上去就行了,默认就是一个jsx,代码片段
}
</div>
</div>
)
}
具名插槽,和作用域插槽
- 具名插槽相当于传入一个对象,对象的key就相当于插槽的名字
- 作用域插槽,相当于也是传入一个对象,每个对象对应key的值,传入一个函数,传入函数的原因,就是为了在子组件使用插槽的地方,把参数通过函数的参数返回回来
- vue3中jsx 和react其实写法也是一样的,不同点就是在子组件用插槽的时候
- vue3通过setup的第二个参数ctx.slots去获取,默认插槽是ctx.slots.default(),react的默认插槽通过props.children获取
- vue3获取具名插槽,和作用域插槽ctx.slots.slot1(666),react中:props.children.slot1()
//react
const TestChild = () => {
const testMainClick = (val) => {
console.log('获取子组件传来的值', val)
}
return (
<div>
TestChild
<MainFn testMainClick={testMainClick} >
{
{
test1(val) {
console.log('slotAAA', val)
return <>
<div>test1__slot</div>
<div>{val}</div>
</>
}
}
}
</MainFn>
</div>
)
}
//子组件
const MainFn = ({children, testMainClick}) => {
const { test1 } = children
console.log('children', children)
const handleMain = () => {
testMainClick('AAA')
}
return (
<div>
<button onClick={handleMain}>MainFn</button>
<div>
{
test1('传递值回去')
}
</div>
</div>
)
}
vue3中插槽的使用
/* 在vue3中用react方式的函数式组件不是响应式的 */
const TestChild = (props) => {
console.log('props', props)
const data = reactive({ city: '重庆' })
const handleChild = () => {
data.city = '成都'
console.log('handleChild', data)
}
return (
<div>
<div>{data.city}</div>
<div>TestChild</div>
<button onClick={handleChild}>改变</button>
</div>
)
}
/* setup格式的函数式组件是响应式的 */
const TestChild2 = {
props: {
city: String
},
setup(props, ctx) {
console.log('propssetup', props, ctx)
const data = reactive({ city: '北京' })
const handleChild = () => {
data.city = '上海'
console.log('handleChild', data)
}
return () => {
return (
<div>
<div>{data.city}</div>
<div>TestChild2</div>
<button onClick={handleChild}>改变</button>
<div>{ctx.slots.default(22)}</div>
<div>{ctx.slots.slot1(666)}</div>
</div>
)
}
}
}
//vuejsx中子组件要向父组件传递参数,通过props.fn()
//如果是模板的方法写,需要定义emits
//方式一
<TestChild
name="AA"
v-slots={{
default(val) {
return <div>TestChildAAA{val}</div>
},
slot1(val) {
return <div>TestChildAAA{val}</div>
}
}}
>
//方式二
<TestChild>
{{
default(val) {
return <div>TestChildAAA{val}</div>
},
slot1(val) {
return <div>TestChildAAA{val}</div>
}
}}
</TestChild>
react-dom
createPortal
通过createPortal
实现把元素插入到节点,比原生的方法性能更好
import ReactDOM from 'react-dom'
ReactDOM.createPortal(
<div className="modal">{this.props.children}</div>,
document.body // DOM 节点
)
prop-types
prop-types
实现props的类型检查
import PropTypes from 'prop-types'
syntheticEvent
- react的event是react自己封装的一个event,
- react16事件绑定到document上的
- react17事件绑定到root组件上,有利于多个react并存,例如微前端
受控组件
- 相当于数据双向绑定
- 在input的值,受data的改变而改变
- onChange去控制input
非受控组件
<input defaultValue={this.state.name} ref={this.inputRef} />
- 在上传文件时,就必须用非受控组件
setState
- 不可变值, 什么时候修改,什么时候去加值,不要提前去加
- 可能是异步更新
- 可能会被合并
- 虽然,通过数组的push这些方法,一样可以渲染成功,但是在shouldeComponentUpdate生命周期中,他们的值还是一样的,这样不能判断比较
//nextProps,改变后的props
// 改变后的当前state值
//通过this.state 与nextState 和props与nextProps比较
//如果要重新渲染,则返回true,否则返回false
shouldComponentUpdate(nextProps, nextState) {
console.log('nextProps', this.state)
console.log('shouldComponentUpdate', nextProps, nextState)
return true
}
this.inputRef = createRef()
<input type='file' ref={this.inputRef} />
console.log('ref', this.inputRef.current.files)
immutable
- 一般不通过深度比较,object,而是通过immutable库实现新的数据结构
原生 html
const rawHtml = '<span>富文本内容<i>斜体</i><b>加粗</b></span>'
const rawHtmlData = {
__html: rawHtml // 注意,必须是这种格式
}
const rawHtmlElem = <div>
<p dangerouslySetInnerHTML={rawHtmlData}></p>
<p>{rawHtml}</p>
</div>
return rawHtmlElem
相当于vue的nextTick
const useSyncCallback = callback => {
const [proxyState, setProxyState] = useState({ current: false })
useEffect(() => {
if (proxyState.current === true) setProxyState({ current: false })
}, [proxyState])
useEffect(() => {
proxyState.current && callback()
})
return useCallback(() => {
setProxyState({ current: true })
}, [proxyState])
}
const onClick = () => {
setCount(count + 1)
nextTick() //执行后,获取最新的数据
}
const nextTick = useSyncCallback(() => {
console.log('改变后', count)
})
HOC高阶组件
import React from 'react'
// 高阶组件
const withMouse = (Component) => {
class withMouseComponent extends React.Component {
constructor(props) {
super(props)
this.state = { x: 0, y: 0 }
}
handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY
})
}
render() {
return (
<div style={{ height: '500px' }} onMouseMove={this.handleMouseMove}>
{/* 1. 透传所有 props 2. 增加 mouse 属性 */}
<Component {...this.props} mouse={this.state}/>
</div>
)
}
}
return withMouseComponent
}
const App = (props) => {
const a = props.a
const { x, y } = props.mouse // 接收 mouse 属性
return (
<div style={{ height: '500px' }}>
<h1>The mouse position is ({x}, {y})</h1>
<p>{a}</p>
</div>
)
}
export default withMouse(App) // 返回高阶函数
renderProps
import React from 'react'
import PropTypes from 'prop-types'
class Mouse extends React.Component {
constructor(props) {
super(props)
this.state = { x: 0, y: 0 }
}
handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY
})
}
render() {
return (
<div style={{ height: '500px' }} onMouseMove={this.handleMouseMove}>
{/* 将当前 state 作为 props ,传递给 render (render 是一个函数组件) */}
{this.props.render(this.state)}
</div>
)
}
}
Mouse.propTypes = {
render: PropTypes.func.isRequired // 必须接收一个 render 属性,而且是函数
}
const App = (props) => (
<div style={{ height: '500px' }}>
<p>{props.a}</p>
<Mouse render={
/* render 是一个函数组件 */
({ x, y }) => <h1>The mouse position is ({x}, {y})</h1>
}/>
</div>
)
/**
* 即,定义了 Mouse 组件,只有获取 x y 的能力。
* 至于 Mouse 组件如何渲染,App 说了算,通过 render prop 的方式告诉 Mouse 。
*/
export default App
forwardRef
//函数组件可以拿到ref,或者高阶组件传递时
forwardRef((props, ref) => {})
react-router-dom
//16.8自动注入的withRouter,所有可以直接通过props去拿到props.location,props.match.params
//也可以通过提供的hooks去拿,useParams,useLocation
//BrowserRouter h5的路由
// HashRouter hash模式
// 跳转通过useHistory props.history.push(`/article/${res.id}`)
// props.history.push(`/?page=1&keyword=${keyword}`)
<BrowserRouter>
//也可以用插槽的方式
<Route path='/testRouter' component={TestRouter}></Route>
<Route path='/ClassRouter/:age' component={ClassRouter}></Route>
</BrowserRouter>
//exact严格匹配,完全相等时,才匹配,在嵌套路由中,二级路由可以使用,一级路由不要使用
//NavLink有一个激活的active activeClassName
//Route有三种渲染方式,通过插槽,通过render,通过component
//一般嵌套路由使用插槽或者render,render可以让子组件拿到props,
<Route path='/Home' render={(props) =>
<div>
//让home拿到props
<Home {...props} />
<Route path='/Home/Child1' exact component={Child1} />
<Route path='/Home/Child2' exact component={Child2} />
</div>
}>
react基于不可变值的设计理念,redux也要不可变值
- hooks也要不可变值
redux
- 需要引入三个包
redux
,redux-thunk
,react-redux
redux
提供注入store的一些方法,和中间件加载函数react-redux
主要用于获取state和dispatch,和Provide的一些组件和方法redux-thunk
处理异步的一个中间件,要处理异步,必须加上这个中间件 ####示例代码
import * as redux from 'redux'
import thunk from 'redux-thunk'
import * as reactRedux from 'react-redux'
import * as reduxThunk from 'redux-thunk'
console.log('redux', redux)
console.log('reactRedux', reactRedux)
console.log('reduxThunk', reduxThunk)
const { createStore, applyMiddleware } = redux
const defaultState = {
name: '张三',
city: '成都',
age: 22,
list: {
yy: 32
}
}
const promiseFn = (query) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('一秒后执行' + query)
}, 1000);
})
}
/* 同步处理 */
export const getName = (list) => {
// 一般直接return,但是为了在返回的函数拿到值,就用异步的方法,这样可以返回一个值
// return {
// type: 'NAME_ACTION',
// payload: list
// }
return (dispatch) => {
dispatch({
type: 'NAME_ACTION',
payload: list
})
return list
}
}
/* 异步处理 */
export const getAsyncName = (list) => {
return (dispatch) => {
return promiseFn(list).then(res => {
dispatch({
type: 'NAME_ACTION',
payload: res
})
return res
})
}
}
const reducer = (state = defaultState, action) => {
// console.log('测试action', action)
if(action.type === 'NAME_ACTION') {
// state.age = action.payload
state.list.yy = action.payload
state = {...state}
}
return state
}
const store = createStore(reducer, applyMiddleware(thunk))
import React from 'react'
import * as reactRedux from 'react-redux'
import { getName, getAsyncName } from '../../../store/index'
// console.log(';reactRedux', reactRedux, getName)
const { useDispatch, useSelector } = reactRedux
const Child1 = (props) => {
const disptch = useDispatch()
const age = useSelector(state => {
// console.log('获取', state.list.yy)
// return state.age
return state.list.yy
})
const handleBtn1 = () => {
const data = disptch(getName('同步触发了,老弟'))
console.log('获取AAA', data)
}
const handleBtn2 = () => {
disptch(getAsyncName(883263)).then(res => {
console.log('res', res)
})
// console.log('获取AAA', age)
}
console.log('Child1的props', props)
return <div>
Child1
<div>AA{age}</div>
<button onClick={handleBtn1}>同步触发redux</button>
<button onClick={handleBtn2}>异步触发redux</button>
</div>
}
export default Child1
export default store
//注入在顶层store
<Provider store={store}>
<App />
</Provider>
react安装ts环境
npx create-react-app my-react-ts --typescript
react中使用typescript
import React from "react";
interface testProps {
message?: string
}
// 可以通过React.FC简写
const Test: React.FunctionComponent<testProps> = (props) => {
return <div>
{props.message}
</div>
}
Test.defaultProps = {
message: '你好啊'
}
export default Test
react中动态添加class
- 安装
npm i classnames