组件实例的三大核心属性
1.state
2.props
3.refs
-
字符串形式的ref
<input ref="input1"/>
-
回调形式的ref
<input ref={c=>{this.input1=c}}/>
-
createRef创建ref容器
myRef=React.createRef() <input ref={this.myRef}/>
React生命周期(旧)
初始化
1、构造器
constructor(props){}
2、组件将要挂载
componentWillMount(){}
3、render
render(){}
4、组件挂载完毕
componentDidmount(){}
卸载组件方法
ReactDOM.unmountComponentAtNode(document.getElementById(''))
5、组件将要卸载
componentWillUnmount(){}
更新
(父组件render)
1、组件props将要更新
componentWillReceiveProps(props){}
(this.setState()或父组件render)
2、是否更新组件
shouldComponentUpdate(){
reutrn <Boolean>
}
(this.setState()或父组件render或强制更新this.forceUpdate())
3、组件将要更新
componentWillUpdate(){}
4、render()
5、组件更新完毕
componentDidUpdate(){}
卸载
1、组件将要卸载
componentWillUnmount(){}
React生命周期(新)
初始化
由ReactDOM.render()触发——初次渲染
1、constructor()
2、getDerivedStateFromProps
从props获取派生状态,当state的值在任何时候都取决于props时,可以使用
3、render()
4、componentDidMount()
组件挂载完毕
更新
由组件内部this.setState()、this.forceUpdate或父组件重新render触发
1、getDerivedStateFromProps
2、shouldComponentUpdate()
组件是否更新(this.forceUpdate()将会跳过此过程)
3、render()
4、getSnapshotBeforeUpdate()
更新前获取快照
5、componentDidUpdate()
组件更新完毕
卸载
由ReactDOM.unmountComponentAtNode()触发
1、componentwillUnmount()
父子组件传值
父传子
-
在父组件中使用子组件时,为其传递属性值
render(){ return ( <div> <Header title={this.status.title}/> </div> ) }
-
在子组件中接收父组件传递过来的属性
//使用 {this.props.propsName} 的方式接收属性 render(){ return ( <div> <h1>{this.props.title}</h1> </div> ) }
**注意:**当你遇到需要同时获取多个子组件数据,或者两个组件之间需要相互通讯的情况时,需要把子组件的 state 数据提升至其共同的父组件当中保存。之后父组件可以通过 props 将状态数据传递到子组件当中。这样应用当中所有组件的状态数据就能够更方便地同步共享了。
路由
-
导航区 通过
Link
标签创建路由链接<Link to="/about">About</Link>
-
展示区通过
Route
标签进行路由匹配import About from './components/about' <Route path="/about" component={About} />
-
使用
BrowserRouter
或HashRouter
将所有路由包裹起来<BrowserRouter><App/></BrowserRouter>
一般组件与路由组件的区别
-
写法不同
//一般组件: <Header/> //路由组件: <Router path="/about" component={About} />
-
存放的位置不同
一般组件:components
路由组件:pages
-
收到的props不同
一般组件:给组件传递什么,就收到什么
路由组件:收到三个固定的属性
history: go: ƒ go(n) goBack: ƒ goBack() goForward: ƒ goForward() push: ƒ push(path, state) replace: ƒ replace(path, state) location: pathname: "/about" search: "" state: undefined match: isExact: true params: {} path: "/about" url: "/about"
路由标签
Link
创建路由链接
<Link to="/about">About</Link>
NavLink
创建路由链接,通过指定activeClassName
,为点击后的导航添加相应类名
<NavLink activeClassName="activeNav" className="list-group-item" to="/about">About</NavLink>
Switch
- 通常情况下,path和component是一一对应的关系
- Switch可以提高路由的匹配效率(单一匹配)
<Switch>
<Route path="/about" component={About} />
<Route path="/home" component={Home} /> //此时只会显示Home组件,不会继续向后匹配路由
<Route path="/home" component={Test} />
</Switch>
路由严格匹配
//默认模糊匹配 Link链接的路径必须包含Route匹配的路径,且顺序要一致
<Link to="/home/a/b">Home</Link>
<Route path="/home" component={Home} /> //此时home可以被匹配到
//开启严格匹配 exact={true} 简写exact
<Link to="/home/a/b">Home</Link>
<Route exact path="/home" component={Home} /> //此时home无法被匹配到
严格匹配不要随便开启,需要时再开,有些时候开启会导致无法匹配二级路由
路由重定向
放在路由末尾,当所有路由都匹配不上时,重定向到指定路由
<Switch>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
<Redirect to="/about"/>
</Switch>
嵌套路由
-
注册子路由时,需要加上父路由的path
<Switch> <Route path='/home/news' component={News} /> <Route path='/home/message' component={Message} /> <Redirect to="/home/news"/> </Switch>
-
路由的匹配是按照注册路由的顺序进行的
/home->/home/news
路由组件传参
1、params参数
首先,在路由链接Link
中传递所需参数,然后在路由Route
中声明需要接收的参数,最后在路由组件中,通过this.params.match
的params
属性获取params参数。
//Message组件
{/* 向路由组件传递params参数 */}
<Link to={`/home/message/detail/${item.id}/${item.title}`}>{item.title}</Link>
{/* 声明接收params参数 */}
<Route path='/home/message/detail/:id/:title' component={Detail} />
//Detail组件
render() {
// 接收params参数
const {id,title}=this.props.match.params
const content=data.find(item=>item.id===id).content
return (
<ul>
<li>id:{id}</li>
<li>title:{title}</li>
<li>content:{content}</li>
</ul>
)
}
2、search参数
-
路由链接(携带参数):
<Link to={`/home/message/detail/?id=01&title=searchParams`}>{item.title}</Link>
-
正常注册路由(无需声明接收参数)
<Route path='/home/message/detail' component={Detail} />
-
接收参数 this.props.location.search
const {search}=this.props.location //search = ?id=01&title=searchParams //注意:获取到的search是urlencoded编码字符串(key1=value1&key2=value2),需要对其进行解析
3、state参数
-
路由链接(携带参数):
<Link to={{pathname:'/home/message/detail',state:{id:'01',title:'stateParams'}}}>{item.title}</Link>
-
正常注册路由(无需声明接收参数)
<Route path='/home/message/detail' component={Detail} />
-
接收参数 this.props.location.state
const {id,title}=this.props.location.state||{} //注意:刷新时可以保留参数,清空缓存直接跳转指定路径时会丢失参数,因为参数保存在浏览器history中
push 与 replace模式
通过为Link
标签设置replace={true}
或简写replace
,可以将路由的模式切换为replace,此时切换路由将会替换当前路由
<Link replace to="/home/message/01/params">{item.title}</Link>
编程式导航
-
this.props.history.push(path, state)
-
this.props.history.replace(path, state)
-
this.props.history.go()
-
this.props.history.goBack()
-
this.props.history.goForward()
replaceShow = (id, title) => {
// replace跳转+params参数
// this.props.history.replace(`/home/message/detail/${id}/${title}`)
// replace跳转+search参数
// this.props.history.replace(`/home/message/detail/?id=${id}&title=${title}`)
// replace跳转+state参数
this.props.history.replace(`/home/message/detail`, { id, title })
}
pushShow = (id, title) => {
return () => {
// push跳转+params参数
// this.props.history.push(`/home/message/detail/${id}/${title}`)
// push跳转+search参数
// this.props.history.push(`/home/message/detail/?id=${id}&title=${title}`)
// push跳转+state参数
this.props.history.push(`/home/message/detail`, { id, title })
}
}
withRouter
-
withRouter可以加工一般组件,让一般组件具备路由组件所特有的API
-
withRouter的返回值是一个新的组件
import React, { Component } from 'react' import { withRouter } from 'react-router-dom' //引入withRouter class Header extends Component { back = () => { this.props.history.goBack() //此时可以使用路由组件上的API } forward = () => { this.props.history.goForward() } render() { return ( <div> <h1>React Router</h1> <button onClick={this.back}>回退</button> <button onClick={this.forward}>前进</button> </div> ) } } export default withRouter(Header)
BrowserRouter与HashRouter的区别
-
底层原理不一样
BrowserRouter使用的是H5的history API,不兼容IE9及以下版本
HashRouter使用的是URL的哈希值
-
path表现形式不一样
BrowserRouter路径中没有#,location:3000/home/message
HashRouter路径中有#,location:3000/#/home/message
-
刷新后对路由state参数的影响
BrowserRouter没有任何影响,因为state保存在history对象中
HashRouter刷新后会导致路由state参数的丢失
-
备注
HashRouter可以用于解决一些路径错误相关的问题(样式丢失)
其他
this.props.children可以获取标签体内容
类似Vue中的插槽
//Header.jsx
<h1>{this.props.children}</h1>
//使用
<Header>标签体内容</Header>
解决多级路径下刷新页面,样式丢失问题
- public/index.html中,引入样式时,不写./xxx,写/xxx (常用)
- public/index.html中,引入样式时,不写./xxx,写%PUBLIC_URL%/xxx (常用,适用于React)
- 使用HashRouter
redux
redux三个核心概念:
-
action
- 动作对象
- 包含两个属性
- type:标识属性,值为字符串,唯一,必要属性
- data:数据属性,值类型任意,可选属性
- 例子:{type:‘ADD_STUDENT’,data:{name:‘xiaoming’,age:12}}
-
reducer
- 用于初始化状态、加工状态
- 加工时,根据旧的state和action,产生新的state的纯函数
-
store
-
将state、action、reducer联系在一起的对象
-
如何得到此对象?
import {createStore} from 'redux' import reducer from './reducers' const store = createStore(reducer)
-
此对象的功能?
- getState():获取State
- dispatch(action):分发action,触发reducer调用,产生新的state
- subscribe(listener):注册监听,当产生了新的state时,自动调用
-
store.getState()
用于获取store中管理的状态
import store from '../../redux/store'
const count = store.getState()
store.dispatch(action)
用于分发action,触发reducer调用
store.dispatch({type:'increment',data:1})
subscribe(listener)
监听state的变化,当state改变时,执行回调函数
import store from './redux/store'
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<React.StrictMode><App /></React.StrictMode>)
store.subscribe(() => {
root.render(<React.StrictMode><App /></React.StrictMode>)
})
redux只负责管理状态,状态改变后界面不会自动更新,需要监测状态改变,自行驱动界面重新渲染
异步action
在创建action时,不再返回一个对象,而是返回一个函数。在函数中执行异步任务,之后再通过调用store.dispatch(),去修改state。
需要在store中引入redux-thunk和applyMiddleware,在创建store时,将applyMiddleware(thunk)作为createStore()的第二个参数传入。此时,store接收到函数类型的action时,会去调用该函数。
//count_action.js
// 异步action 返回的action值为函数,异步任务中一般都会调用同步action
export const createIncrementAsyncAction = (data, delay) => {
return (dispatch) => {
setTimeout(() => {
dispatch(createIncrementAction(data))
}, delay)
}
}
React拓展
1. setState
setState更新状态的2种写法
(1). setState(stateChange, [callback])------对象式的setState
1.stateChange为状态改变对象(该对象可以体现出状态的更改)
2.callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用
(2). setState(updater, [callback])------函数式的setState
1.updater为返回stateChange对象的函数。
2.updater可以接收到state和props。
4.callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。
总结:
1.对象式的setState是函数式的setState的简写方式(语法糖)
2.使用原则:
(1).如果新状态不依赖于原状态 ===> 使用对象方式
(2).如果新状态依赖于原状态 ===> 使用函数方式
(3).如果需要在setState()执行后获取最新的状态数据,
要在第二个callback函数中读取
2. lazyLoad
路由组件的lazyLoad
//1.通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
const Login = lazy(()=>import('@/pages/Login'))
//2.通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading界面
<Suspense fallback={<h1>loading.....</h1>}>
<Switch>
<Route path="/xxx" component={Xxxx}/>
<Redirect to="/login"/>
</Switch>
</Suspense>
3. Hooks
1. React Hook/Hooks是什么?
(1). Hook是React 16.8.0版本增加的新特性/新语法
(2). 可以让你在函数组件中使用 state 以及其他的 React 特性
2. 三个常用的Hook
(1). State Hook: React.useState()
(2). Effect Hook: React.useEffect()
(3). Ref Hook: React.useRef()
3. State Hook
(1). State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作
(2). 语法: const [xxx, setXxx] = React.useState(initValue)
(3). useState()说明:
参数: 第一次初始化指定的值在内部作缓存
返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
(4). setXxx()2种写法:
setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值
4. Effect Hook
默认情况下,React 会在每次渲染后调用副作用函数 —— 包括第一次渲染的时候。
(1). Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
(2). React中的副作用操作:
发ajax请求数据获取
设置订阅 / 启动定时器
手动更改真实DOM
(3). 语法和说明:
useEffect(() => {
// 在此可以执行任何带副作用操作
return () => { // 在组件卸载前执行
// 在此做一些收尾工作, 比如清除定时器/取消订阅等
}
}, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行
(4). 可以把 useEffect Hook 看做如下三个函数的组合
componentDidMount()
componentDidUpdate()
componentWillUnmount()
5. Ref Hook
(1). Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
(2). 语法: const refContainer = useRef()
(3). 作用:保存标签对象,功能与React.createRef()一样
4. Fragment
使用
<Fragment><Fragment>
<></>
作用
可以不用必须有一个真实的DOM根标签了
5. Context
理解
一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信
使用
1) 创建Context容器对象:
const XxxContext = React.createContext()
2) 渲染子组时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据:
<xxxContext.Provider value={数据}>
子组件
</xxxContext.Provider>
3) 后代组件读取数据:
//第一种方式:仅适用于类组件
static contextType = xxxContext // 声明接收context
this.context // 读取context中的value数据
//第二种方式: 函数组件与类组件都可以
<xxxContext.Consumer>
{
value => ( // value就是context中的value数据
要显示的内容
)
}
</xxxContext.Consumer>
注意
在应用开发中一般不用context, 一般都用它的封装react插件
6. 组件优化
Component的2个问题
只要执行setState(),即使不改变状态数据, 组件也会重新render() ==> 效率低
只当前组件重新render(), 就会自动重新render子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低
效率高的做法
只有当组件的state或props数据发生改变时才重新render()
原因
Component中的shouldComponentUpdate()总是返回true
解决
办法1:
重写shouldComponentUpdate()方法
比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false
办法2:
使用PureComponent
PureComponent重写了shouldComponentUpdate(), 只有state或props数据有变化才返回true
注意:
只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false
不要直接修改state数据, 而是要产生新数据
项目中一般使用PureComponent来优化
7. render props
如何向组件内部动态传入带内容的结构(标签)?
Vue中:
使用slot技术, 也就是通过组件标签体传入结构 <A><B/></A>
React中:
使用children props: 通过组件标签体传入结构
使用render props: 通过组件标签属性传入结构,而且可以携带数据,一般用render函数属性
children props
<A>
<B>xxxx</B>
</A>
{this.props.children}
问题: 如果B组件需要A组件内的数据, ==> 做不到
render props
<A render={(data) => <C data={data}></C>}></A>
A组件: {this.props.render(内部state数据)}
C组件: 读取A组件传入的数据显示 {this.props.data}
8. 错误边界
理解:
错误边界(Error boundary):用来捕获后代组件错误,渲染出备用页面
特点:
只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
使用方式:
getDerivedStateFromError配合componentDidCatch
// 生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {
console.log(error);
// 在render之前触发
// 返回新的state
return {
hasError: true,
};
}
componentDidCatch(error, info) {
// 统计页面的错误。发送请求发送到后台去
console.log(error, info);
}
9. 组件通信方式总结
组件间的关系:
- 父子组件
- 兄弟组件(非嵌套组件)
- 祖孙组件(跨级组件)
几种通信方式:
1.props:
(1).children props
(2).render props
2.消息订阅-发布:
pubs-sub、event等等
3.集中式管理:
redux、dva等等
4.conText:
生产者-消费者模式
比较好的搭配方式:
父子组件:props
兄弟组件:消息订阅-发布、集中式管理
祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。