React(五)
- 一、受控组件
- 1.什么是受控组件(v-model)
- 2.收集表单数据:input和单选框
- 3.收集表单数据:下拉框
- 二、非受控组件
- 三、高阶组件
- 1.什么是高阶组件
- 2.高阶组件的应用1
- 3.高阶组件的应用2-注入Context
- 4.高阶组件的应用3-登录鉴权
- 5.高阶组件的应用4-生命周期劫持
- 四、Portals的使用
- 五、Fragment的用法和短语
- 六、React中的CSS
- 1.内联样式style
- 2.CSS模块化编写方式
- 3.less的使用方案
- 4.scss的使用方案
- 5.CSS in JS
- (1)ES6模板字符串调用函数
- (2)使用styled-components的步骤
- (3)模板字符串props接收数据
- (4)attrs设置属性默认值
- (5)styled-components高级特性
- 6.React中动态添加class
一、受控组件
1.什么是受控组件(v-model)
类似vue中的v-model,就是双向数据绑定。
比如下面这两个input框,受控组件的条件就是有value
值。
但是只有value值读取state中的值不行,这样input
里的东西就不能改了,所以要有onChange
事件去修改相应的值,实现输入框和state
数据的同步。
export class App extends PureComponent {
constructor() {
super();
this.state = {
name: 'zzy',
age: 18,
}
}
changeInput(e) {
console.log(e.target.value);
this.setState({
name: e.target.value
})
}
render() {
let { name } = this.state;
return (
<div>
{/* 1.受控组件 */}
<input type="text" value={name} onChange={(e) => this.changeInput(e)}/>
{/* 2.非受控组件 */}
<input type="text" />
<h1>{name}</h1>
</div>
)
}
}
2.收集表单数据:input和单选框
React文档写的非常非常好,去看看
下面是一个提交用户名密码和单选框选中状态的案例
下面这个案例定义了两个输入框,分别是用户名和密码。还定义了一个单选框,有几个需要注意的地方。
-
for由于对应js关键字,使用htmlFor代替
-
受控组件的特点,上面已经提到,必须写value且必须要有
onChange
事件,且该事件要修改对应的数据。 -
修改数据时我们可以去读取
e.target.name
找到相应的数据,把值修改为e.target.value
,这样的话就不用一个一个修改了。(键值读取变量用[]
包裹) -
提交的值我们可以根据表单的类别进行限制,如果是
checkbox
那么就收集选中状态(checked
属性),如果是输入框那么就收集value
-
点击按钮提交表单数据的时候,数据已经是最新的了(onChange的时候就setState => render了),那么我们可以拿着最新的数据去发送ajax请求。
export class App extends PureComponent {
constructor() {
super();
this.state = {
userName: '',
password: 0,
isAgree: false,
}
}
submitData() {
console.log('提交', this.state.userName, this.state.password,this.state.isAgree)
}
changeInput(e) {
console.log(e.target.value);
//如果拿单选框的数据,那么拿到的是布尔值checked
const value = e.target.type == 'checkbox' ? e.target.checked : e.target.value;
this.setState({
[e.target.name]: value
})
}
render() {
let { userName, password,isAgree } = this.state;
return (
<div>
<form action="">
{/* 1.用户名 */}
<label htmlFor="userName">用户名</label>
<input
type="text"
id='userName'
name='userName'
value={userName}
onChange={(e) => this.changeInput(e)}
/>
{/* 2.密码 */}
<label htmlFor="password"></label>
<input
type="password"
id='password'
name='password'
value={password}
onChange={(e) => this.changeInput(e)}
/>
{/* 3.单选框 */}
<input
type="checkbox"
name="isAgree"
id="man"
value="同意"
checked={isAgree}
onChange={(e) => this.changeInput(e)}
/>
<label htmlFor="man">同意上述协议</label>
</form>
<button onClick={() => this.submitData()}>提交表单数据</button>
</div>
)
}
}
3.收集表单数据:下拉框
<select>
<option value="apple">苹果</option>
<option selected value="pear">莉</option>
<option value="peach">桃子</option>
</select>
请注意,由于 selected 属性的缘故,莉选项默认被选中。React 并不会使用 selected 属性,而是在根 select 标签上使用 value 属性。这在受控组件中更便捷,因为您只需要在根标签中更新它。例如:
export class App extends PureComponent {
constructor() {
super();
this.state = {
fruit: 'apple'
}
}
submitData() {
console.log('提交', this.state.fruit)
}
handleFruitChange(e) {
this.setState({
fruit: e.target.value
})
}
render() {
let { fruit } = this.state;
return (
<div>
<form action="">
<select value={fruit} onChange={(e) => this.handleFruitChange(e)}>
<option value="apple">苹果</option>
<option value="pear">莉</option>
<option value="peach">桃子</option>
</select>
</form>
<button onClick={() => this.submitData()}>提交表单数据</button>
</div>
)
}
}
这里如果select标签加了个multiple,那么意味着可以收集多个value,我们可以借助e.target.selectedOptions
这个伪数组获取所有被选中的选项,拿到它们的值
handleFruitChange(e) {
const options = Array.from(e.target.selectedOptions);
const values = options.map(item => item.value);
this.setState({
fruit: values
})
}
二、非受控组件
一般不用非受控组件,因为这玩意儿就是直接操作DOM
- 在非受控组件中通常使用
defaultValue
来设置默认值; - 如果要使用非受控组件中的数据,那么我们需要使用 ref 来从DOM节点中获取表单数据。
export default class App extends PureComponent {
constructor(props) {
super(props);
this.myRef = createRef();
}
render() {
return (
<div>
<form onSubmit={e => this.handleSubmit(e)}>
<label htmlFor="username">
用户:
<input type="text" id="username" defaultValue='默认值' ref={this.myRef}/>
</label>
<input type="submit" value="提交"/>
</form>
</div>
)
}
handleSubmit(event) {
event.preventDefault();
console.log(this.myRef.current.value);
}
}
三、高阶组件
高阶函数:接收一个函数作为参数,或者返回一个函数。满足两个条件之一。
JavaScript中比较常见的filter、map、reduce都是高阶函数。
1.什么是高阶组件
高阶组件的英文是 Higher-Order Components,简称为 HOC。
官方的定义:高阶组件是参数为组件,返回值为新组件
的函数
;
也就是说,首先, 高阶组件本身不是一个组件,而是一个函数
;其次,这个函数的参数是一个组件
,返回值也是一个组件
;
举个例子:
//高阶组件:接收一个组件作为参数,返回另一个组件
function high(Component) {
class newComponent extends React.PureComponent {
render() {
return (
//可以做一些处理,比如统一在这里添加name属性
<Component name='zzy'/>
)
}
}
return newComponent;
}
//需要添加name属性的组件
class HelloWorld extends React.PureComponent {
render() {
console.log(this.props.name);//输出name
return (
<div>HelloWorld</div>
)
}
}
//1.返回一个类组件
const Dj = high(HelloWorld)
export class App extends React.PureComponent {
render() {
return (
<div>
{/* 2.对类进行实例化 */}
<Dj/>
</div>
)
}
}
2.高阶组件的应用1
看下面这个例子,定义两个普通组件Component1、Component2
,我们要给这两个组件的props上塞点数据,让它实例化的时候带着这个数据去展示,怎么做呢?
答案是定义高阶组件enhanceUserInfo
,返回一个新组件,这个新组件把你传进来的组件作为儿子,并且把自己身上的数据给他{...this.state.userInfo}
,这样的话经过处理的Component1、Component2
就可以拥有name
和age
,可以通过props读到。
function enhanceUserInfo(OriginComponent) {
class NewComponent extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
userInfo: {
name: 'zzy',
age: 18
}
}
}
render() {
//把一些数据塞到你传进来的组件中
return <OriginComponent {...this.props} {...this.state.userInfo}/>
}
}
return NewComponent
}
function Component1(props) {
return <h1>Component1: {props.name}-{props.age}-{props.flag}</h1>
}
const NewCom1 = enhanceUserInfo(Component1);
//也可以这样写
const NewCom2 = enhanceUserInfo(function(props) {
return <h1>Component2: {props.name}-{props.age}-{props.sex}</h1>
})
export class HOC extends PureComponent {
render() {
return (
<div>
<h2>HOC</h2>
{/* 如果在这里传值的话,传给的是NewComponent */}
<NewCom1 flag='标识'/>
<NewCom2 sex='男'/>
</div>
)
}
}
还有个问题,如果我们往新组件中添加属性的话,是传给谁了呢?答案是传给了高阶组件中新组件newComponent
的props
中,我们可以通过{...this.props}
再把它们传给你塞进去的组件OriginComponent
3.高阶组件的应用2-注入Context
细品一下,其实高阶组件的作用就是在我们要处理的组件上边包个父组件,父组件给它加点料,然后再把父组件扔出来用。
比如我们如果在App中使用context
给下面两个子组件传值:
export class App extends PureComponent {
render() {
return (
<div>
<h1>父组件App</h1>
<myContext.Provider value={{ name: 'zzy', age: 18 }}>
<HOC/>
<HOC2/>
</myContext.Provider>
</div>
)
}
}
那么很多组件都要接到这个数据,如果我们分别取每个子组件定义Consumer
去接那真的是非常麻烦,此时我们就可以把注入value
的这个操作封装一下,也就是使用高阶组件。
function enhanceContext(Component) {
class NewComponent extends PureComponent {
render() {
return (
<div>
<myContext.Consumer>
{
value => {
return <Component {...value} />
}
}
</myContext.Consumer>
</div>
)
}
}
return NewComponent;
}
那么我们在HOC和HOC2中就可以通过高阶组件注入value,并拿到传进来的value值:
export class HOC extends PureComponent {
render() {
return (
<div>
<h2>子组件1:HOC</h2>
<h3>HOC拿到传过来的value:{this.props.name + this.props.age}</h3>
</div>
)
}
}
const newHOC = enhanceContext(HOC);
export default newHOC;
function HOC2(props) {
return (
<div>
<h2>子组件2:HOC2</h2>
<h3>HOC2拿到穿过来的value:{props.name}-{props.age}</h3>
</div>
)
}
export default enhanceContext(HOC2);
4.高阶组件的应用3-登录鉴权
如果我们要实现,HOC和HOC2等组件在登录之后才能显示:
export class App extends PureComponent {
render() {
return (
<div>
<h1>父组件App</h1>
{/* 没登陆的话不展示,只有登录了才展示 */}
<HOC />
<HOC2 />
</div>
)
}
}
定义高阶组件,拦截要展示的组件,进行鉴权:
function loginAuth(Component) {
return (props) => {
if(localStorage.getItem('token')) {
return <Component {...props}/>
}else {
return <h2>没有登录,{Component.name}不展示,请先登录!</h2>
}
}
}
export default loginAuth;
然后这两个组件导出的时候,调用一下就行了,然后把新的组件导出,新的这个组件来决定页面要展示什么东西:
export class HOC extends PureComponent {
render() {
return (
<div>
<h2>子组件1:HOC</h2>
</div>
)
}
}
const newHOC = loginAuth(HOC);
export default newHOC;
function HOC2(props) {
return (
<div>
<h2>子组件2:HOC2</h2>
</div>
)
}
export default loginAuth(HOC2);
结果:
5.高阶组件的应用4-生命周期劫持
比如我还可以在高阶组件中计算每个组件的挂载时间
function computeInterval(Component) {
return class extends PureComponent {
componentWillMount() {
this.begin = Date.now();
}
componentDidMount() {
this.over = Date.now();
let interval = this.over - this.begin;
console.log(`${Component.name}组件渲染时间为${interval}ms`)
}
render() {
return <Component {...this.props}/>
}
}
}
四、Portals的使用
Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案:
ReactDOM.createPortal(React元素,哪个容器)
import {createPortal} from 'react-dom';
export class App extends PureComponent {
render() {
return (
<div>
<h1>App渲染在root容器中</h1>
{createPortal(<h2>我是h2我要渲染在zzy容器中</h2>, document.querySelector('#zzy'))}
</div>
)
}
}
应用:创建一个Model组件,让组件标签中间插槽的元素全部进入model容器中,该容器是一个固定定位:
<div id="root"></div>
<div id="zzy"></div>
<div id="model"></div>
#model {
position:fixed;
left: 50%;
top: 50%;
background-color: skyblue;
transform: translate(-50%, -50%);
}
import {createPortal} from 'react-dom';
import Model from './Model';
export class App extends PureComponent {
render() {
return (
<div>
<h1>App渲染在root容器中</h1>
{createPortal(<h2>我是h2我要渲染在zzy容器中</h2>, document.querySelector('#zzy'))}
<Model>
<h1>数据结构</h1>
<h2>算法</h2>
<h3>冒泡排序</h3>
</Model>
</div>
)
}
}
import {createPortal} from 'react-dom';
export class Model extends PureComponent {
render() {
return (
<div>
<h1>Model</h1>
{createPortal(this.props.children, document.querySelector('#model'))}
</div>
)
}
}
五、Fragment的用法和短语
每次我们写结构都要包一个根div,并且这个div会在浏览器元素中显示
如果不想在浏览器中显示,可以引入Fragment:
import React, { PureComponent, Fragment } from 'react';
export class App extends PureComponent {
render() {
return (
<Fragment>
<h1>奥里给</h1>
<h2>不用根</h2>
<h3>嗷嗷嗷</h3>
</Fragment>
)
}
}
语法糖:不用引入,直接<></>
render() {
return (
<>
<h1>奥里给</h1>
<h2>不用根</h2>
<h3>嗷嗷嗷</h3>
</>
)
}
但是这种语法糖在遍历需要绑定key
的时候不能写
,必须要写完整的标签才行噢~
六、React中的CSS
1.内联样式style
就是直接写行内style,要求是传入一个对象,而且属性名要以小驼峰命名,好处是可以读取state中的变量。
export class App extends PureComponent {
constructor() {
super();
this.state = {
size: 20
}
}
large() {
this.setState({
size: this.state.size + 5
})
}
render() {
let {size} = this.state;
return (
<div>
<h1 style={{color:'red', fontSize: '30px'}}>奥里给</h1>
<h2 style={{color:'skyblue', fontSize:`${size}px`}}>样式</h2>
<button onClick={() => this.large()}>增大上面的字体</button>
</div>
)
}
}
2.CSS模块化编写方式
如果我们把每个组件的css分别定义相应的文件然后引入,那么也是没用的,所有的css文件第一次引入后
都会默认是全局css
,不管写哪个文件夹里,都是共享类名,如果后续还有其他css文件包含相同类名引入,同类名样式会被下一个渲染的依次覆盖。
但是我们组件开发肯定是希望每个组件有自己的样式,这一点Vue做的很好,可以加个scope
,但是React咋办呢?
这里可以用css模块化的编写方案:
第一步:名字前面加.module
.box {
border: 1px solid red;
}
第二步:需要用这里样式的组件中引入:
import appStyle from './App.module.css'
第三步:使用.属性名
来读取我们对应的类名
{/* 2.CSS模块化*/}
<div className={appStyle.box}>
<h1>嗷嗷嗷</h1>
</div>
真的是非常麻烦啊,不过好像用的人还挺多的。
3.less的使用方案
参考Ant Design关于安装craco和less的配置
安装carco(create-react-app config缩写)和craco-less
npm install craco craco-less
修改package.json
/* package.json */
"scripts": {
- "start": "react-scripts start",
- "build": "react-scripts build",
- "test": "react-scripts test",
👇👇👇👇👇👇
+ "start": "craco start",
+ "build": "craco build",
+ "test": "craco test",
}
然后在项目根目录创建一个 craco.config.js
用于修改默认配置。
//craco.config.js文件
const CracoLessPlugin = require('craco-less')
module.exports = {
plugins: [
{
plugin: CracoLessPlugin,
}
]
}
ok,现在就可以在React中定义less并引入使用了
4.scss的使用方案
react中使用scss:
npm add node-sass@npm:dart-sass
5.CSS in JS
安装第三方库styled-components:
npm i styled-components
(1)ES6模板字符串调用函数
我们先补充一个知识,就是模板字符串竟然也能tmd调用函数,参数的构成是字符串先构成一个数组作为第一个参数,然后后面的参数是引用的name
和age
的值
function fun(...arg) {
console.log(arg);
}
let name = 'zzy';
let age = 18;
fun`我叫${name},我今年${age}岁`;
(2)使用styled-components的步骤
为了方便演示,我们定义一个组件,写上类名
export class App extends PureComponent {
render() {
return (
<div>
<h1 className='nav'>导航栏</h1>
<div className='box'>
<h2 className='title'>标题</h2>
<p className='content'>内容内容</p>
</div>
</div>
)
}
}
定义一个style.js文件,里面写:
import styled from 'styled-components';
const NiuBi = styled.div`
.nav {
color: red;
}
.box {
border: 1px solid black;
.title {
font-size: 40px;
&:hover {
background-color: blue;
}
}
.content {
color: skyblue;
}
}
`
export default NiuBi;
是的你没看错,这样就借助这个styled-components
的库创建了一个作为div并带着样式的NiuBi
组件。
紧接着我们引入NiuBi
,让他作为根节点包住元素,这样的话,NiuBi
里面的所有样式,我们都可以在style.js
中去写,而且不会和其他地方有冲突。
import NiuBi from './style';
export class App extends PureComponent {
render() {
return (
<NiuBi>
<h1 className='nav'>导航栏</h1>
<div className='box'>
<h2 className='title'>标题</h2>
<p className='content'>内容内容</p>
</div>
</NiuBi>
)
}
}
补充:为了写css方便,可以安装vscode的一个插件:
(3)模板字符串props接收数据
还有一个没见过的操作,css里因为用的是模板字符串,我们可以通过${}
去读取变量,由于我们这个库返回的这个NiuBi
是一个组件,那么我们可以通过组件标签给它传一些数据,读取的方式就是一个立即调用的箭头函数props => props.color
(4)attrs设置属性默认值
写法如下:参数是一个回调,回调的参数是props。可以给一些属性设置默认值,如果传了就用传的值,如果没传就用默认值。
(5)styled-components高级特性
1、支持样式的继承:
2、可以设置主题,然后下面的地方如果用到styled-components
包裹就可以通过props.theme
去读取主题的样式:
6.React中动态添加class
那么在React中怎么办呢?我们可以:
但是这样也比较繁琐,所以我们可以借助一个第三方库:classnames
1、安装:
npm install classnames
2、导入:
import classNames from 'classnames';
3、使用:
<h2 className={classNames('title',{active:true})}>标题</h2>
更多使用方式:
官方文档连接:https://github.com/JedWatson/classnames