文章目录
- 一、受控组件
- 1. 什么是受控组件
- 2. 收集input框内容
- 3. 收集checkBox的值
- 4. 下拉框select
- 总结
- 二、非受控组件
- 三、高阶组件
- 1. 高阶组件的概念 (回顾高阶函数)
- 2. 高阶组件应用:注入props
- (1) 高阶组件给---函数式组件注入props
- (2) 高阶组件给---类组件注入props
- (3) 高阶组件注入props+自己需要注入props
- 3. 高阶组件应用:context的增强
- 4. 高阶组件应用:登录鉴权
- 5. 高阶组件应用:生命周期劫持
- 总结
- 四、 Portals
- 五、 Fragment组件
- 六、 严格模式StrictMode
一、受控组件
1. 什么是受控组件
当表单元素绑定value
属性时,就是受控组件,此时按键盘也无法再向输入框里输入信息
若要输入信息,必须绑定onChange
事件,获取输入的值,修改state,然后通过value属性将修改后的值显示到输入框里。(很像vue中的v-model)
2. 收集input框内容
(1) form
标签上绑定onSubmit
事件,当提交时,触发该事件。
(2) label
标签里,htmlFor
属性值为所绑定的表单属性的id
值
(3) 两个不同类型输入框的处理事件合并到一个函数中。e.target.name
的值是input标签里的name
属性。e.target.value
是输入框里的值。(键值读取变量用[]
包裹)。
// 提交事件
handleSubmitClick (e) {
// 1.阻止默认的行为
e.preventDefault()
// 2.获取到所有的表单数据, 对数据进行组件
console.log("获取所有的输入内容:", this.state.username, this.state.password)
// 3.以网络请求的方式, 将数据传递给服务器(ajax/fetch/axios)
}
// 处理input框输入
handleInputChange (e) {
this.setState({
[e.target.name]: e.target.value
})
}
// 渲染
render () {
const { username, password } = this.state
return (
<div>
<form onSubmit={e => this.handleSubmitClick(e)}>
<label htmlFor="username">
用户:
<input type="text" id="username" name='username'
value={username}
onChange={e => this.handleInputChange(e)} />
</label><br />
<label htmlFor="password">
密码:
<input type="password" name="password" id="password"
value={password}
onChange={e => this.handleInputChange(e)} />
</label><br />
<button type='submit'>注册</button>
</form>
</div>
)
}
3. 收集checkBox的值
单选框
(1) 通过checked
属性设置该框是否被选中。实现数据改变页面
(2) 读取e.target.checked
来获取用户的输入,判断用户点击后,checkbox是true还是false
handleAgreeChange (e) {
// 根据用户输入修改状态值
this.setState({
isAgree: e.target.checked
})
}
{/* 单选框 this.state.isAgree默认值是false */}
const { isAgree } = this.state
<label htmlFor="agree">
<input
id='agree'
type="checkbox"
checked={isAgree}
onChange={e => this.handleAgreeChange(e)}
/>
同意协议
</label><br />
多选框
多选框就是循环遍历数据。与单选框处理逻辑类似,注意修改hobbies时,需要浅拷贝。
hobbies: [
{ value: "sing", text: "唱", isChecked: false },
{ value: "dance", text: "跳", isChecked: false },
{ value: "rap", text: "rap", isChecked: true }
],
{/* 多选框 */}
{hobbies.map((item, index) => {
return (
<label htmlFor={item.value} key={item.value}>
<input
type="checkbox"
id={item.value}
checked={item.isChecked}
onChange={(e) => this.handleHobbiesChange(e, index)} />
{item.text}
</label>
)
})}
// 注意需要浅拷贝
handleHobbiesChange (e, index) {
console.log(e.target.checked, index);
const hobbies = [...this.state.hobbies]
hobbies[index].isChecked = e.target.checked
this.setState({ hobbies })
}
4. 下拉框select
单选下拉框
(1)通过value
属性来设置默认值,fruit: "apple",
(2) e.target.value
来读取用户选了什么水果;
{/* 下拉框 */}
{/* value={fruit} 设置默认值 */}
<select value={fruit} onChange={e => this.handleFruitChange(e)}>
<option value="orange">橘子</option>
<option value="apple">苹果</option>
<option value="banana">香蕉</option>
</select>
// 水果下拉框
handleFruitChange (e) {
this.setState({ fruit: e.target.value })
}
多选下拉框
(1)添加multiple
属性,设置多选,此时绑定的value值为 fruits: ['apple', 'banana']
。
(2)e.target.selectedOptions
获取用户选择,具体看事件处理函数
{/* 多选框 */}
<select value={fruits} onChange={e => this.handleFruitsChange(e)} multiple>
<option value="orange">橘子</option>
<option value="apple">苹果</option>
<option value="banana">香蕉</option>
</select>
// 水果下拉多选框
handleFruitsChange (e) {
const options = Array.from(e.target.selectedOptions)
// options里每一项的value值是用户选择的值
const values = options.map(item => item.value)
this.setState({ fruits: values })
// 额外补充: Array.from(可迭代对象)
// Array.from(arguments)
const values2 = Array.from(e.target.selectedOptions, item => item.value)
console.log(values2)
}
总结
Element | Value property | Change callback | New value in the callback |
---|---|---|---|
<input type="text" /> | value="string" | onCahnge | e.target.value |
<input type="checkbox" /> | checked={boolean} | onCahnge | e.target.checked |
<input type="radio" /> | checked={boolean} | onCahnge | e.target.checked |
<textarea/> | value="string" | onCahnge | e.target.value |
<select/> | value="option value" | onCahnge | e.target.value |
二、非受控组件
(1) 绑定默认值不能用value
,会变成受控组件。应该用defaultValue
(2) 给元素添加监听事件,只能用原生的方式; 首先需要绑定ref
,然后添加监听事件addEventListener
(3) 通过this.introRef.current.value
来获取输入框里的值。
(4) checkbox
和radio
支持defaultChecked
;select
和textarea
支持defaultValue
export class App extends PureComponent {
constructor() {
super()
this.state = {
intro: '123456'
}
this.introRef = createRef()
}
componentDidMount () {
this.introRef.current.addEventListener(...)
}
// 提交事件
handleSubmitClick (e) {
// 1.阻止默认的行为
e.preventDefault()
// 2.获取到所有的表单数据, 对数据进行组件
console.log(this.introRef.current.value);
// 3.以网络请求的方式, 将数据传递给服务器(ajax/fetch/axios)
}
// 渲染
render () {
const { intro } = this.state
return (
<div>
<form onSubmit={e => this.handleSubmitClick(e)}>
{/* 非受控组件 */}
<input type="text" defaultValue={intro} ref={this.introRef} />
<button type='submit'>注册</button>
</form>
</div >
)
}
}
三、高阶组件
1. 高阶组件的概念 (回顾高阶函数)
(1) 高阶函数
满足这两个条件之一的就是高阶函数:
接受一个或多个函数作为输入;
输出一个函数;
比如JS中的map,filter,reduce
funcition foo(){
function bar(){
}
return bar
}
// foo函数返回一个函数,所以这个也是高阶函数
const fn = foo()
(2) 高阶组件(Higher-Order Components) 简称HOC
高阶组件本质是一个函数(并不是组件),这个函数的参数是组件,函数的返回值是一个新组件。
// 1. 定义一个原组件
class OriginHW extends PureComponent {
render () {
return (
<div>HelloWorld</div>
)
}
}
// 2. 定义一个高阶组件,接收原组件,并对原组件进行一些操作
function hoc (Cpn) {
class newHW extends PureComponent {
render () {
return <Cpn name="why" />
}
}
return newHW
}
// 3. 调用高阶组件,这里的参数直接传原组件的名称;接收到的一个新组件
const HelloHOC = hoc(OriginHW)
class App extends PureComponent {
render () {
return (
<div>
{/* 使用新组件 */}
<HelloHOC />
</div>
)
}
}
界面打印:HelloWorld;
可以看出,高阶组件对原来的组件进行了一层拦截,拦截之后就可以对组件进行一些操作,再返回组件。
高阶组件不是React API的一部分,是一种设计模式。
高级组件再一些React第三方库中十分常见:
* 比如:redux中的connect;
* 比如:react-router中的withRouter;
2. 高阶组件应用:注入props
首先定义一个高阶组件,用于给需要特殊数据的组件,注入props数据。比如某些组件需要userInfo这个数据。
src/hoc/enhancedUserInfo.js
:
// 定义高阶组件,给需要特殊数据的组件,注入props
function enhancedUserInfo (OriginComponent) {
class NewComponent extends PureComponent {
// 构造函数
constructor() {
super()
this.state = {
userInfo: {
userName: 'tom',
age: '18'
}
}
}
render () {
return (
// 通过props的方式将数据传给组件
<OriginComponent {...this.state.userInfo} />
)
}
}
// 其实本质上return的是注入了props数据的<OriginComponent/>组件
return NewComponent
}
export default enhancedUserInfo
(1) 高阶组件给—函数式组件注入props
App.jsx
import React, { PureComponent } from 'react'
import enhancedUserInfo from './hoc/enhancedUserInfo'
import About from './pages/About'
// 增强函数式组件,props接收数据,
// funtion后面可以不写函数名,这里是为了区分增强前后,Friend与NewFriend
const NewFriend = enhancedUserInfo(function Friend (props) {
return <h2>Hello--{props.userName}---{props.age}---{props.fruit}</h2>
})
// App应用函数式组件
class App extends PureComponent {
render () {
...
<NewFriend />
}
}
(2) 高阶组件给—类组件注入props
src/pages/About.jsx
:创建类组件About
import enhancedUserInfo from '../hoc/enhancedUserInfo'
class About extends PureComponent {
render () {
return (
<div>
{/* 没有构造函数也可以读取this.props */}
<h2>About---{this.props.userName}---{this.props.age}</h2>
</div>
)
}
}
// 在导出的时候,利用高阶组件增强改类组件,让该组件收到props数据
export default enhancedUserInfo(About)
App.jsx
// App应用函数式组件
class App extends PureComponent {
render () {
...
<About/>
}
}
(3) 高阶组件注入props+自己需要注入props
在使用子组件之前,通过高阶组件可以注入一些props数据;如果在App中使用子组件时,也往里传递了数据,该怎么接收。
class App extends PureComponent {
render () {
return (
<div>
{/* fruit实际是传给了高阶组件里的NewComponent */}
<NewFriend fruit={['apple', 'banana']} />
<About fruit={['橘子', '火龙果']} />
</div>
)
}
}
这里传递的fruit,实际上是传到了enhancedUserInfo
里的NewComponent
类中,然后再通过props的方式传给OriginComponent
.
原组件中仍然是通过props
来接收这些数据
3. 高阶组件应用:context的增强
照旧先创建theme-context
src/context/theme-context.js
import { createContext } from 'react'
const ThemeContext = createContext()
export default ThemeContext
App中应用子组件Product,并用Context包裹子组件,传递数据
class App extends PureComponent {
render () {
return (
<div>
{/* 使用新组件 */}
<ThemeContext.Provider value={{ color: 'red', size: 20 }}>
<Product />
</ThemeContext.Provider>
</div >
)
}
}
子组件接收context数据的方式有两种,指定context类型的只能接收一种context数据。所以一般通过consumer来接收多个context数据。之前的做法是在组件内部使用consumer:
这样的会让组件可读性不高,且不好维护。
利用高阶组件的做法:在高阶组件中使用consumer,然后将context数据注入原组件中。
(1) 定义高阶组件withTheme;
src/hoc/with-theme.js
:
import ThemeContext from "../context/theme-context"
function withTheme (OriginComponent) {
return (props) => {
return (
<ThemeContext.Consumer>
{
value => {
// 通过props将context数据传给子组件
return <OriginComponent {...value} />
}
}
</ThemeContext.Consumer>
)
}
}
export default withTheme
(2) 高阶组件在子组件向外暴露的时候拦截一下,注入数据:
import withTheme from '../hoc/with-theme'
export class Product extends PureComponent {
render () {
return (
// props接收数据。
<div>Product---color:{this.props.color}---size:{this.props.size}</div>
)
}
}
export default withTheme(Product)
4. 高阶组件应用:登录鉴权
(1) 定义一个高阶组件。判断当前是否有token
,有就说明登录了(登录就渲染界面),没有就没登录(没登录就返回提示信息)
src/hoc/login-auth .js
:
function loginAuth (OriginComponent) {
// 返回一个函数式组件
return (props) => {
const token = localStorage.getItem('token')
if (token) { // 登录则渲染该组件
return <OriginComponent />
} else { // 没登录则给出提示信息
return <h2>请先登录</h2>
}
}
}
export default loginAuth
(2) 哪个子组件需要登录鉴权,就用高阶组件拦截一下
子组件Cart:
import loginAuth from '../hoc/login-auth'
export class Cart extends PureComponent {
render () {
return (
<div>Cart</div>
)
}
}
export default loginAuth(Cart)
(3) App中使用子组件
App中设置一个state数据:isLogin,用来渲染数据的变化。
class App extends PureComponent {
...
login () {
localStorage.setItem('token', 'tom')
// 设置isLogin变量的目的是,确定登录,修改数据,能够重新调用render函数
this.setState({ isLogin: true })
// 如果没有isLogin,可以调用强制刷新的API,但是也不建议使用这个API
// this.forceUpdate()
}
render () {
return (
<div>
<Cart />
<button onClick={e => this.login()}>点击登录</button>
</div >
)
}
}
高阶函数里什么时候适合创建类组件,什么适合适合创建函数组件。
5. 高阶组件应用:生命周期劫持
通过生命周期来计算每个组件的挂载时间。
export class Detail extends PureComponent {
UNSAFE_componentWillMount () {
this.beginTime = new Date().getTime()
}
componentDidMount () {
this.endTime = new Date().getTime()
const interval = this.endTime - this.beginTime
console.log(`当前页面花费了${ interval }ms渲染完成!`)
}
render () {...}
}
抽取到高阶组件中:
// 可以对需要计算渲染时间的组件进行拦截
function logRenderTime (OriginComponent) {
// 函数式组件没有生命周期,所以返回一个类组件;
// 由于是直接返回这个类,所以类名可以省略 class NewComponent extends...
return class extends PureComponent {
UNSAFE_componentWillMount () {
this.beginTime = new Date().getTime()
}
componentDidMount () {
this.endTime = new Date().getTime()
const interval = this.endTime - this.beginTime
console.log(`当前${ OriginComponent.name }页面花费了${ interval }ms渲染完成!`)
}
render () {
return <OriginComponent {...this.props} />
}
}
}
export default logRenderTime
OriginComponent.name
可以拿到组件的名字;
应用在某个组件上:
import logRenderTime from '../hoc/log_render_time'
export class Detail extends PureComponent {
...
}
export default logRenderTime(Detail)
总结
高阶组件主要是方便代码的复用。
高阶组件中什么时候返回类组件,什么时候返回函数式组件,取决于是否
四、 Portals
某些情况下,我们希望渲染的内容独立于父组件,挂载到其他位置。甚至独立于当前挂载的DOM元素中。
<!--比如当前除了root根节点,还有第二个节点。通过Portals,我们可以将内容挂载到root2中-->
<div id="root"></div>
<!-- 新节点 -->
<div id="root2"></div>
App.jsx
createPortal(child,container)
:child
是任何可渲染的React元素,container
是DOM元素,也就是需要挂载的地方。
// 1. 引入createPortal函数
import { createPortal } from 'react-dom'
export class App extends PureComponent {
render () {
return (
<div>
<h1>AppH1</h1>
{
createPortal(<h2>App H2</h2>, document.querySelector('#root2')
)
}
</div>
)
}
}
export default App
五、 Fragment组件
因为要求只能有一个根节点,所以每次写结构都要包裹一个div
。
当我们不想多一个div
结构时,可以采用Fragment
import React, { Fragment, PureComponent } from 'react'
export class App extends PureComponent {
render () {
return (
// <div>
// <h1>没吃早饭</h1>
// <h2>没吃午饭</h2>
// <h3>没吃晚饭</h3>
// </div>
<Fragment>
<h1>没吃早饭</h1>
<h2>没吃午饭</h2>
<h3>没吃晚饭</h3>
</Fragment>
)
}
}
Fragment
标签可以用是用<></>
代替(语法糖)。需要注意,当需要在Fragment
标签绑定key
属性时,不能采用语法糖的形式。
<>
<h1>没吃早饭</h1>
<h2>没吃午饭</h2>
<h3>没吃晚饭</h3>
</>
六、 严格模式StrictMode
StrictMode于Fragment一样,不会渲染到结构上面。主要用来显示程序中潜在的问题。严格模式的检查尽在开发模式下运行。
import React, { PureComponent, StrictMode } from 'react'
export class App extends PureComponent {
render () {
return (
<div>
{/* 对Home及其后代元素开启严格模式 */}
<StrictMode>
<Home />
</StrictMode>
<Profile />
</div>
)
}
}
严格模式检查什么?
(1) 识别不安全的生命周期,检查是否使用过时的ref API
// Home.jsx
UNSAFE_componentWillMount () {
console.log("Home UNSAFE_componentWillMount")
}
render () {
return (
<div>
<h2 ref="title">Home Title</h2>
{/* <h2>Home</h2> */}
</div>
)
}
严格模式下,会报错,提醒程序员,避免一些隐藏的bug。
如果Profile
组件使用这两个过时的API及生命周期函数,仍可正常使用,不会报错误。
(2) 检查副作用
严格模式检查下的组件的生命周期函数会被调用两次,以检查此处的逻辑代码当被多次调用是,是否会产生bug。在生产环境中是不会被调用两次的。
(3)检查是否使用其他废弃或过时(findDOMNode)的方法 ,给出警告