React中获取DOM的方式有哪些?
ref 获取DOM
- 方式一:传入字符串
- 使用时通过 this.refs.传入的字符串格式获取对应的元素;
- 方式二:传入一个对象
- 对象是通过 React.createRef() 方式创建出来的;
- 使用时获取到创建的对象其中有一个current属性就是对应的元素;
- 方式三:传入一个函数
- 该函数会在DOM被挂载时进行回调,这个函数会传入一个 元素对象,我们可以自己保存;
- 使用时,直接拿到之前保存的元素对象即可;
getDOM() {
// 方式一: 在react元素上绑定ref字符串 - 这种方式react已经不推荐了
// console.log(this.refs.http)
// 方式二: 提前创建好ref对象, createRef(), 将创建出来的对象绑定到元素(推荐)
// console.log(this.titleRef.current)
// 方式三: 传入一个回调函数, 在对应的元素被渲染之后, 回调函数被执行, 并且将元素传入(16.3之前的版本)
// console.log(this.titleEl)
}
<h3 ref="http">大大怪将军</h3>
<h3 ref={this.titleRef}>小小怪下士</h3>
<h3 ref={el => this.titleEl = el}>瑞克</h3>
<button onClick={() => this.getDOM()}>获取DOM</button>
ref 的类型
-
ref 的值根据节点的类型而有所不同:
- 当 ref 属性用于 HTML 元素时,构造函数中使用
React.createRef()
创建的 ref 接收底层 DOM 元素作为其 current 属性; - 当 ref 属性用于自定义 class 组件时,ref 对象接收组件的挂载实例作为其
current
属性; - 不能在函数组件上使用 ref 属性,因为没有实例;
- 当 ref 属性用于 HTML 元素时,构造函数中使用
-
函数式组件是没有实例的,所以无法通过ref获取他们的实例:
- 但是某些时候,我们可能想要获取函数式组件中的某个DOM元素;
- 这个时候我们可以通过
React.forwardRef
,后面我们也会学习 hooks 中如何使用ref;
ref 获取组件实例 -- createRef
import React, { PureComponent, createRef } from 'react'
constructor() {
super()
this.state = {}
this.HWRef = createRef()
}
getComponent() {
console.log(this.HWRef.current)
this.HWRef.current.test()
}
<HelloWorld ref={this.HWRef} />
<button onClick={() => this.getComponent()}>获取组件实例</button>
函数组件 -- 函数式组件是没有实例的,所以无法通过ref获取他们的实例 -- React.forwardRef
import React, { PureComponent, createRef, forwardRef } from 'react'
const HelloWorld = forwardRef(function(props, ref) {
return (
<div>
<h2 ref={ref}>函数组件</h2>
<h4>大大怪将军</h4>
</div>
)
})
constructor() {
super()
this.state = {}
this.HWRef = createRef()
}
getComponent() {
console.log(this.HWRef.current)
}
render() {
return (
<div>
<HelloWorld ref={this.HWRef} />
<button onClick={() => this.getComponent()}>获取DOM</button>
</div>
)
}
受控组件和非受控组件
受控组件与非受控组件的主要区别就在于数据的来源。受控组件的数据源是React 组件的
state
属性,而非受控组件的数据源是DOM 元素本身
受控组件
- 在 React 中,可变状态通常保存在组件的
state
属性中,并且只能通过使用setState()
来更新- 我们将两者结合起来,使React的
state
成为“唯一数据源” - 渲染表单的 React 组件还控制着用户输入过程中表单发生的操作
- 被 React 以这种方式控制取值的表单输入元素就叫做"受控组件"
- 我们将两者结合起来,使React的
this.state = {
message: ""
}
changeInput(event) {
console.log(event.target.value)
this.setState({ message: event.target.value })
}
render(){
<
input type="text"
value={message}
onChange={(event) => this.changeInput(event)}
/>
}
非受控组件
- 在受控组件中,表单数据是由 React 组件来管理的
- 非受控组件中,表单数据将交由 DOM 节点来处理
- 在非受控组件中通常使用
defaultValue
来设置默认值; <input type="checkbox">
和<input type="radio">
支持defaultChecked
,<select>
和<textarea>
支 持defaultValue
。
- 在非受控组件中通常使用
this.messageRef.current.value
// 在非受控组件中通常使用defaultValue来设置默认值
render(){
<input type="text" defaultValue={message} ref={this.messageRef} />
}
处理多个输入和默认行为
1.多个输入
- 多处理方式可以像单处理方式那样进行操作,但是需要多个监听方法:
- 这里我们可以使用ES6的一个语法:计算属性名(Computed property names)
[event.target.name]: event.target.value
方便进行事件监听
2.默认行为
- DOM默认处理HTML表单的行为,在用户点击提交时会提交到某个服务器中,并且刷新页面;
- 在React中,并没有禁止这个行为,依然是有效的
event.preventDefault()
阻止
export class App extends PureComponent {
constructor() {
super()
this.state = {
username: "",
password: ""
}
}
handleSubmitClick(event) {
// 1.阻止默认的行为
event.preventDefault()
// 2.获取到所有的表单数据, 对数据进行组件
console.log("获取所有的输入内容")
console.log(this.state.username, this.state.password)
// 3.以网络请求的方式, 将数据传递给服务器(ajax/fetch/axios)
}
handleInputChange(event) {
this.setState({
[event.target.name]: event.target.value
})
}
render() {
const { username, password } = this.state
return (
<div>
<form onSubmit={e => this.handleSubmitClick(e)}>
{/* 1.用户名和密码 */}
<label htmlFor="username">
用户:
<input
id='username'
type="text"
name='username'
value={username}
onChange={e => this.handleInputChange(e)}
/>
</label>
<label htmlFor="password">
密码:
<input
id='password'
type="password"
name='password'
value={password}
onChange={e => this.handleInputChange(e)}
/>
</label>
<button type='submit'>注册</button>
</form>
</div>
)
}
}
高阶函数
-
至少满足以下条件之一:
- 接受一个或多个函数作为输入;
- 输出一个函数;
-
常见的filter、map、reduce都是高阶函数
高阶组件(HOC)
高阶组件是参数为组件,返回值为新组件的函数
就是传入一个组件,对这个组件进行一些功能的增强,再返回出来新的组件
- 注意: 首先 高阶组件 本身不是一个组件,而是一个函数 其次,这个函数的参数是一个组件,返回值也是一个组件
- HOC 是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式
- 高阶组件在一些React第三方库中非常常见:
- 比如redux中的connect;
- 比如react-router中的withRouter;
- 高阶组件在一些React第三方库中非常常见:
// 定义一个高阶组件
function hoc(Cpn) {
// 1.定义类组件
class NewCpn extends PureComponent {
render() {
return <Cpn name="why"/>
}
}
//组件名称可以通过 displayName 来修改
NewCpn.displayName = "NewComponet"
return NewCpn
// 定义函数组件
// function NewCpn2(props) {
// }
// return NewCpn2
}
class HelloWorld extends PureComponent {
render() {
return <h1>Hello World</h1>
}
}
//调用过程
const HelloWorldHOC = hoc(HelloWorld)
export class App extends PureComponent {
render() {
return (
<div>
<HelloWorldHOC/>
</div>
)
}
}
高级组件应用的场景
- props的增强
// 定义组件: 给一些需要特殊数据的组件, 注入props
function enhancedUserInfo(OriginComponent) {
class NewComponent extends PureComponent {
constructor(props) {
super(props)
this.state = {
userInfo: {
name: "coderwhy",
level: 99
}
}
}
render() {
return <OriginComponent {...this.props} {...this.state.userInfo}/>
}
}
return NewComponent
}
//调用
const Home = enhancedUserInfo(function(props) {
return <h1>Home: {props.name}-{props.level}-{props.banners}</h1>
})
//传入数据
export class App extends PureComponent {
render() {
return (
<div>
<Home banners={["轮播1", "轮播2"]}/>
</div>
)
}
}
- 利用高阶组件来共享Context
//1.高阶组件-实现数据共享
function withTheme(OriginComponment) {
return (props) => {
return (
<ThemeContext.Consumer>
{
value => {
return <OriginComponment {...value} {...props}/>
}
}
</ThemeContext.Consumer>
)
}
}
export default withTheme
//2.调用
export class Product extends PureComponent {
render() {
const { color, size } = this.props
return (
<div>
<h2>Product: {color}-{size}</h2>
</div>
)
}
}
export default withTheme(Product)
//传入数据
export class App extends PureComponent {
render() {
return (
<div>
<ThemeContext.Provider value={{color: "red", size: 30}}>
<Product/>
</ThemeContext.Provider>
</div>
)
}
}
-
渲染判断鉴权
//封装实现鉴权 function loginAuth(OriginComponent) { return props => { // 从localStorage中获取token const token = localStorage.getItem("token") if (token) { return <OriginComponent {...props}/> } else { return <h2>请先登录, 再进行跳转到对应的页面中</h2> } } } export default loginAuth
- 生命周期劫持
```js
/**
* 高阶组件:用于计算 React 组件的渲染时间,并将结果打印到控制台中。
*在 `UNSAFE_componentWillMount` 钩子函数中,我们记录了组件渲染开始的时间戳。
*在 `componentDidMount` 中,我们记录了组件渲染结束的时间戳,并计算出渲染时间的间隔。
* @param {React.ComponentType} OriginComponent - 要增强的组件
* @returns {React.ComponentType} 增强后的组件
*/
function logRenderTime(OriginComponent) {
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() {
// 渲染原始组件,并传递所有 props 属性
return <OriginComponent {...this.props}/>
}
}
}
export default logRenderTime
- ....
HOC也有自己的一些缺陷:
- HOC需要在原组件上进行包裹或者嵌套,如果大量使用HOC,将会产生非常多的嵌套,这让调试变得非常困难;
- HOC可以劫持props,在不遵守约定的情况下也可能造成冲突;
ref的转发
- 在开发中我们可能想要获取函数式组件中某个元素的DOM
- 通过
forwardRef
高阶函数
- 通过
const HelloWorld = forwardRef(function(props, ref) {
return (
<div>
<h1 ref={ref}>Hello World</h1>
<p>哈哈哈</p>
</div>
)
})
export class App extends PureComponent {
constructor() {
super()
this.hwRef = createRef()
}
getComponent() {
console.log(this.hwRef.current)
}
render() {
return (
<div>
<HelloWorld ref={this.hwRef}/>
<button onClick={e => this.getComponent()}>获取组件实例</button>
</div>
)
}
}
Portals的使用
- 某些情况下,我们希望渲染的内容独立于父组件,甚至是独立于当前挂载到的DOM元素中(默认都是挂载到id为root的DOM元素上的)。
- Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案:
- 第一个参数(
child
)是任何可渲染的 React 子元素,例如一个元素,字符串或fragment
; - 第二个参数(
container
)是一个 DOM 元素;
- 第一个参数(
//modal组件
export class Modal extends PureComponent {
render() {
return createPortal(this.props.children, document.querySelector("#modal"))
}
}
export default Modal
// 在app组件中使用 Portals
export class App extends PureComponent {
render() {
return (
<div className='app'>
<h1>App H1</h1>
{
createPortal(<h2>App H2</h2>, document.querySelector("#why"))
}
{/* 2.Modal组件 */}
<Modal>
<h2>我是标题</h2>
<p>我是内容, 哈哈哈</p>
</Modal>
</div>
)
}
}
fragment
- 可以不适用
div
包裹在最外层,使用fragment
代替
render() {
const { sections } = this.state
return (
<>
<h2>我是App的标题</h2>
<p>我是App的内容, 哈哈哈哈</p>
<hr />
{
sections.map(item => {
return (
<Fragment key={item.title}>
<h2>{item.title}</h2>
<p>{item.content}</p>
</Fragment>
)
})
}
</>
)
}
StrictMode
-
StrictMode 是一个用来突出显示应用程序中潜在问题的工具:
- 与 Fragment 一样,StrictMode 不会渲染任何可见的 UI;
- 它为其后代元素触发额外的检查和警告;
- 严格模式检查仅在开发模式下运行; 它们不会影响生产构建 ;
-
严格模式作用
- 识别不安全的生命周期:
- 使用过时的ref API
- 检查意外的副作用 这个组件的constructor会被调用两次; 这是严格模式下故意进行的操作,让你来查看在这里写的一些逻辑代码被调用多次时,是否会产生一些副作用; 在生产环境中,是不会被调用两次的;
- 使用废弃的findDOMNode方法 在之前的React API中,可以通过findDOMNode来获取DOM,不过已经不推荐使用了,可以自行学习演练一下
- 检测过时的context API 早期的Context是通过static属性声明Context对象属性,通过getChildContext返回Context对象等方式来使用Context的
-
可以为应用程序的任何部分启用严格模式:
- 不会对 Header 和 Footer 组件运行严格模式检查;
- 但是,
ComponentOne
和ComponentTwo
以及它们的所有后代元素都将进行检查