章节回顾:
表单
受控组件
什么是"受控组件?"
官方文档解释:
简单概括为:
表单值的获取和更新操作,交由react中的state来管理
比如:
<input type="text" />
<textarea></textarea>
<select></select>
使用示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="root"></div>
<!-- 开发模式下用development版本 -->
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script type="text/babel">
const {useState} = React
const {createRoot} = ReactDOM
function Inp() {
const [val, setVal] = useState('')
const handleChange = e => {
let v = e.target.value
setVal(v)
}
const handleSubmit = e => {
alert('提交的内容:' + val)
setVal('')
e.preventDefault()
}
return <form onSubmit={handleSubmit}>
<input type="text" value={val} onChange={handleChange} />
<br />
<input type="submit" value="提交" />
</form>
}
const wrap = document.querySelector('#root')
const root = createRoot(wrap)
root.render(<Inp />)
</script>
</body>
</html>
非受控组件
什么是"非受控组件"?
用ref从DOM中获取表单数据,也就是要“直接操作DOM”
比如
<input type="file" />
获取用户上传照片详情示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="root"></div>
<!-- 开发模式下用development版本 -->
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script type="text/babel">
const {useRef} = React
const {createRoot} = ReactDOM
function Inp() {
const inp = useRef(null)
const handleSubmit = e => {
// 上传的文件内容
console.log(inp.current.files)
e.preventDefault()
}
return <form onSubmit={handleSubmit}>
<input type="file" ref={inp} />
<br />
<input type="submit" value="提交" />
</form>
}
const wrap = document.querySelector('#root')
const root = createRoot(wrap)
root.render(<Inp />)
</script>
</body>
</html>
上传图片后,点击提交,可以看到用户上传的图片信息:
总结一下:
受控组件就是把表单的值和操作都交给state来统一管理,
非受控组件就是直接操作DOM,用ref来管理。
二者都是操控表单组件的方式,很多情况下,
一个表单,既可以用受控组件方式处理,又可以用非受控组件方式处理。
value or defaultValue ?
二者都可以在初始状态给表单设置默认值/初始值。
但是,有一点区别。
在表单中,如果你只需要给表单设置一个默认值,
而不去管他后续的更新,这时候就用defaultValue。
不然就会报错:
也就是说,你不用去获取这个value来更新表单的值的时候,就用defaultValue。
具体可以看下面状态提升中的示例。
状态提升
什么是状态提升?
官方文档给出的解释:
在 React 中,将多个组件中需要共享的 state 向上移动到它们的最近共同父组件中,便可实现共享 state。
这就是所谓的“状态提升”。
换句话说,就是:
子传父。
把一个子组件的值传给父组件,
然后通过父组件,把这个值再传给其他子组件使用。
子传父
子传父流程
- 父组件创建一个需要传参的函数
- 哪个子组件要做状态提升,即子组件的值要传给父组件,就把该函数名先传给该子组件
- 该子组件通过
props
接收到该函数,要传值给父组件时,调用该函数,并把值作为参数传入 - 父组件通过该函数的参数,获取子组件传来的值
- 子传父完成
状态提升完整示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="root"></div>
<!-- 开发模式下用development版本 -->
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script type="text/babel">
const {useState} = React
const {createRoot} = ReactDOM
function Child1(props) {
const {getValue} = props
const [val1, setVal1] = useState('')
const handleChange1 = e => {
let v = e.target.value
setVal1(v)
// 3. 值变更时,调用函数,传入参数v
getValue(v)
}
return <input type="text" value={val1} onChange={handleChange1} />
}
function Child2(props) {
// 6. 获取到父组件传来的值
const {f} = props
// 7. 把值交给表单。整个状态提升完成。
return <input type="text" defaultValue={f} />
}
function Father(props) {
const [f, setF] = useState('')
// 1. 创建一个需要传参的函数,用于获取子组件的值
const getValue = (v) => {
// 4. 获取到子组件传来的值,更新该值
setF(v)
}
return <div>
// 2. 把函数传给子组件
input1: <Child1 getValue={getValue} /><br />
<br />
// 5. 把从子组件1获取到的值,传给子组件2
input2: <Child2 f={f} />
</div>
}
const wrap = document.querySelector('#root')
const root = createRoot(wrap)
root.render(<Father />)
</script>
</body>
</html>
组合 vs 继承
在父组件中,常常包含一些拥有未知内容的子组件,
此时,就需要一些特殊方法来渲染它们。
这些特殊方法,就有组合和继承。 先来说说组合。
组合
props.children
props.children表示在父组件中的子组件标签中所包含的所有内容
示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="root"></div>
<!-- 开发模式下用development版本 -->
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script type="text/babel">
const {createRoot} = ReactDOM
function Child(props) {
console.log('props: ', props)
return <div>
{props.children}
</div>
}
function Father() {
return <div>
<Child color="pink">
<h3>
hello react !
</h3>
<p>test here ~</p>
</Child>
</div>
}
const wrap = document.querySelector('#root')
const root = createRoot(wrap)
root.render(<Father />)
</script>
</body>
</html>
来看下子组件中打印出来的props:
这个props包含了子组件标签内联的color
同时还有一个children
属性,包含了标签中的DOM元素。
把props.children
中进行渲染,可以在页面中看到渲染的页面结果。
slot插槽
在vue中,有slot插槽概念。但是在react中,没有这一概念。这里的slot,只是借助了vue中的说法。
react中的"slot",表示的是,把组件当作prop传递给子组件
。
示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="root"></div>
<!-- 开发模式下用development版本 -->
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script type="text/babel">
const {createRoot} = ReactDOM
function Com1() {
return <h3>
hello react !
</h3>
}
function Com2() {
return <p>test here ~</p>
}
function Child(props) {
return <div>
<div className="left">{props.left}</div>
<div className="rig">{props.rig}</div>
</div>
}
function Father() {
return <div>
{/*把组件当作prop传递给子组件*/}
<Child left={<Com1 />} rig={<Com2 />} />
</div>
}
const wrap = document.querySelector('#root')
const root = createRoot(wrap)
root.render(<Father />)
</script>
</body>
</html>
继承
话不多说, 官方文档就给提供了这句话,应该就明白啥意思了。
但是,有些公司老项目,还是用到了继承。
举个例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="root"></div>
<!-- 开发模式下用development版本 -->
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script type="text/babel">
const {createRoot} = ReactDOM
class Father extends React.Component {
render() {
return <div>
hello react !
</div>
}
}
class Child extends Father {}
const wrap = document.querySelector('#root')
const root = createRoot(wrap)
root.render(<Child />)
</script>
</body>
</html>
在实际开发中,如果遇到,至少可以看懂。