【React】React全家桶(二)组件+组件三大核心属性state-props-refs+事件处理与条件渲染+列表与表单+状态提升与组合+高阶函数与函数+函数柯里化

1 组件

组件:从概念上类似于 JS 函数。它接受任意的入参(即 “props”),并返回用于描述页面展示内容的 React 元素。

React-dom是React剥离出的涉及DOM操作的部分

DOM (文档对象模型),是用来呈现以及与任意HTML或XML文档交互API 。 DOM 是载入到浏览器中的文档模型,以节点树的形式来表现文档,每个节点代表文档的构成部分(页面元素、字符串或注释等)。

BOM是浏览器对象模型,是对浏览器本身进行操作,DOM是文档对象模型,是对浏览器(可看成容器)内的内容进行操作。

1.1 函数组件

函数组件:是一个有效的 React 组件,它本质上就是 JavaScript 函数,因为它接收唯一带有数据的 props(代表属性)对象与并返回一个 React 元素

// 1. 创建函数式组件
function MyComponent(props){//入参
  console.log(this); //此处的this是undefined,因为babel编译后开启了严格模式
  return <h2>Hello,{props.name}</h2>
}
// 当 React 元素为用户自定义组件时,它会将JSX所接收的属性(attributes)以及子组件(children)转换为单个对象传递给组件,这个对象被称之为 “props”。 
// 传参
const element = <MyComponent name="Sara" />;
// 2. 渲染组件到页面
ReactDOM.render(
  element,
  document.getElementById('root')
);

执行了ReactDOM.render之后,发生了什么?

  1. 调用 ReactDOM.render() 函数,并传入 <MyComponent name="Sara" /> 作为参数。
  2. React 调用 MyComponent 组件,并将{name: 'Sara} 作为 props 传入。
  3. MyComponent 组件将 Hello, Sara 元素作为返回值。
  4. React DOM 将返回的虚拟DOM转为真实DOM,呈现在页面中,高效地更新为 Hello, Sara

在这里插入图片描述

相关补充:严格模式中的this:

//如果不开启严格模式,直接调用函数,函数中的this指向window
//如果开启了严格模式,直接调用函数,函数中的this是undefined

function sayThis() {
  console.log(this)
}
sayThis() // Window {...}

function sayThis2() {
  'use strict'
  console.log(this)
}
sayThis2() // undefined

注:

  • 组件名必须首字母大写
  • 虚拟DOM元素只能有一个根元素
  • 虚拟DOM元素必须有结束标签

1.2 类式组件

//1.创建类式组件
class MyComponent extends React.Component {
  render(){
	//render放在MyComponent的原型对象上,供实例使用。this指向MyComponent组件实例对象。
	console.log('render中的this:',this);
	return <h2>Hello,{props.name}</h2>
  }
}
const element = <MyComponent name="Sara" />;
// 2. 渲染组件到页面
ReactDOM.render(
  element,
  document.getElementById('root')
);

执行了ReactDOM.render之后,发生了什么?

  1. React解析组件标签,找到了MyComponent组件。
  2. 发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法。
  3. 将render返回的虚拟DOM转为真实DOM,随后呈现在页面中。

从图中可以看出,MyComponent组件实例对象中包含state、props、refs三大属性
在这里插入图片描述

相关补充:ES6中类的注意事项:

  • 类中的构造器不是必须要写的,要对实例进行一些初始化的操作,如添加指定属性时才写。
  • 如果A类继承了B类,且A类中写了构造器,那么A类构造器中的super是必须要调用的。
  • 类中所定义的方法,都放在了类的原型对象上,供实例去使用。

2 组件三大核心属性state-props-refs

2.1 state

2.1.1 关于state的理解
  • React 把组件看成是一个状态机(State Machines)。通过与用户的交互,实现不同状态,然后渲染 UI,让用户界面和数据保持一致。
  • state 是在组件内被组件自己管理的(类似于在一个函数内声明的变量), 简单说就是该组件所存储的数据(状态) state 是私有的,并且完全受控于当前组件,state值是对象(可以包含多个key-value的组合)。
  • state 代表了随时间会产生变化的数据,应当仅在实现交互时使用 , 组件可以选择把它的 state 作为 props 向下传递到它的子组件中
  • 在React 里,只需更新组件的 state,然后根据新的 state 重新渲染用户界面(不要操作 DOM)。
2.1.2 类式组件中的使用state
class Weather extends React.Component{
  constructor(props) {
    super(props)//构造器是否接收props,是否传递给super,取决于:是否希望在构造器中通过this访问prop
   //在构造函数中定义state也可以
   // this.state = {weather: "炎热"} 
  }
  //定义state 给类的实例对象添加属性且赋值
  state = {
      weather: "炎热"
  }
  render() {
    // 读取状态
    // this为组件实例对象
    return <h1>今天天气很{this.state.weather}</h1>
  }
}
ReactDOM.render(<Weather/>, document.getElementById('test'))

类式组件中定义state的方法:

  • 在构造器中定义state

  • 在类中添加属性state定义

如何使用state:

  • 通过this.state调用里面的值

如何修改 state?

  • 在类式组件的函数中,不能直接修改state

    // 不允许
    this.state.weather = '凉爽' 
    
  • 通过类的原型对象上的方法 setState() ,setState是一个同步的方法,但是setState引起React后续更新状态的动作是异步的, setState是一种合并操作,不是替换操作

    //partialState: 状态改变对象  callback: 可选的回调函数,状态更新完毕后调用
    this.setState(partialState, [callback])
    
    // 写法一:直接修改 页面为“凉爽”,但是拿到的状态还是“炎热”
    this.setState({
        weather: "凉爽"
    })
    
    //写法二:在更新完状态后拿到该状态,使用回到函数
    this.setState({state.weather: "凉爽"}()=>{
        
    });
    

2.2 props

2.2.1 关于props的理解
  • 每个组件对象都会有props(properties)属性,组件标签的所有属性都保存在props中

  • props 是传递给组件的(类似于函数的形参),是父组件向子组件传递数据的方式

  • props是通过标签属性组件外组件内传入的数据(从外部传入组件内), 且组件内不可修改props数据,是只读的

2.2.2 类式组件中使用 props
class Person extends React.Component{
  render() {
    return (
      <ul>
        // 入参
        <li>姓名:{this.props.name}</li> 
        <li>性别:{this.props.sex}</li> 
        <li>年龄:{this.props.age}</li>
      </ul>
    )
  }
}

//对标签属性进行类型、必要性的限制
static.propTypes = {
  name:PropTypes.string.isRequired, // 限制name必传,且为字符串
  sex:PropTypes.string, // 限制sex为字符串
  age:PropTypes.number, // 限制age为数值
  speak:PropTypes.func, // 限制speak为函数
}
// 指定默认的标签属性
//指定默认标签属性值
static.defaultProps = {
	sex:'男', // sex默认值为男
	age:18 //age默认值为18
}

// 传参
const element = <Person name: 'zs', age: 18, sex: '男'/>

ReactDOM.render(element, document.getElementById('test'))

在使用的时候可以通过 this.props来获取值 类式组件的 props:

  • 通过在组件标签上传递值,在组件中就可以获取到所传递的值

  • 在构造器里的props参数里可以获取到 props

  • 可以分别设置 propTypesdefaultProps 两个属性来分别操作 props的规范和默认值,两者都是直接添加在类式组件的原型对象上的(所以需要添加 static) (需引入prop-types库)

  • 可以通过...运算符来简化

    const p = {name: 'zs', age: 18, sex: '男'}
    const element = <Person {...p}/>
    
2.2.3 函数式组件使用props

函数组件的 props定义:

  • 函数在使用props的时候,是作为入参进行使用的
  • 在组件标签中传参
  • props的限制和默认值同样设置在原型对象上
//创建组件
function Person (props){//入参
  //从props解构出对应参数
  const {name,age,sex} = props
    return (
      <ul>
        <li>姓名:{name}</li>
        <li>性别:{sex}</li>
        <li>年龄:{age}</li>
      </ul>
    )
}

// 传参
const element = <Person name: 'zs', age: 18, sex: '男'/>

ReactDOM.render(element, document.getElementById('test'))

2.2.4 props和state的区别
  • props 是传递给组件的(类似于函数的形参),是父组件向子组件传递数据的方式

  • state 是在组件内被组件自己管理的(类似于在一个函数内声明的变量), 代表了随时间会产生变化的数据,应当仅在实现交互时使用

  • 组件可以选择把它的 state 作为 props 向下传递到它的子组件中

  • state是组件自身的状态,而props则是外部传入的数据

2.3 refs

2.3.1 关于props的理解
  • 组件内的标签可以定义ref属性来标识自己
  • ref提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素。
2.3.2 操作refs的方法
  • 字符串形式(已过时)

    //定义
    <input ref="input1"/>
    //使用
    this.refs.input1
    
    
  • 回调形式

    //定义
    <input ref={ c => this.input1 = c } />
    //使用
    this.input1
    
    
  • createRef形式(推荐):React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器是“专人专用”的

    //定义
    myRef = React.createRef() 
    <input ref={this.myRef}/>
    //使用
    this.myRef.current
    
    

注意: 不要过度的使用 ref,如果发生时间的元素刚好是需要操作的元素,就可以使用事件对象 event.target 去替代。

3 事件处理与条件渲染

3.1 事件处理

原则一:通过onXxx属性指定事件处理函数(注意大小写)

  • React使用的是自定义事件,采用小驼峰式(camelCase), 使用JSX 语法时传入一个函数
  • React中的事件是通过事件委托方式处理的(委托给组件最外层的元素),更高效
// 传统DOM元素
<button onclick="activateLasers()">
  Activate Lasers
</button>

// React元素
<button onClick={activateLasers}>
  Activate Lasers
</button>

原则二:通过event.target得到发生事件的DOM元素对象

  • 当发生事件的元素是需要操作的元素时,可以通过event.target得到发生事件的DOM元素对象,避免使用ref
//创建组件
class Demo extends React.Component{
  //创建ref容器
  myRef = React.createRef()

  //展示左侧输入框的数据
  showData = (event)=>{
    console.log(event.target); // <button>点我提示左侧的数据</button>
    alert(this.myRef.current.value);
  }

  //展示右侧输入框的数据
  showData2 = (event)=>{
    alert(event.target.value);
  }

  render(){
    return(
	  <div>
		<input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>&nbsp;
		<button onClick={this.showData}>点我提示左侧的数据</button>&nbsp;
		<input onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>&nbsp;
	  </div>
	)
  }
}
//渲染组件到页面
ReactDOM.render(<Demo />,document.getElementById('test'))

3.2 条件渲染

条件渲染: 在 React 中,你可以创建不同的组件来封装各种你需要的行为。然后,依据应用的不同状态,你可以只渲染对应状态下的部分内容 。

方法一: 声明一个变量并使用 if 语句

function UserGreeting(props) {
  return <h1>Welcome back!</h1>;
}
function GuestGreeting(props) {
  return <h1>Please sign up.</h1>;
}

function Greeting(props) {//入参
  const isLoggedIn = props.isLoggedIn;
  if (isLoggedIn) {
    return <UserGreeting />;
  }
  return <GuestGreeting />;
}

ReactDOM.render(
   //传参
  <Greeting isLoggedIn={false} />,
  document.getElementById('root')
);

方法二:与运算符 &&

通过花括号包裹代码,你可以在 JSX 中嵌入任何表达式,这其中也包括与运算符 &&

表达式1 && 表达式2

  • 第一个表达式的值为真,则返回表达式2的值

  • 第一个表达式的值为假,则返回表达式1 的值

function Mailbox(props) {
  const unreadMessages = props.unreadMessages;
  return (
    <div>
      {unreadMessages.length > 0 &&
        <h2>
          You have {unreadMessages.length} unread messages.
        </h2>
      }
    </div>
  );
}

const messages = ['React', 'Re: React', 'Re:Re: React'];
ReactDOM.render(
  <Mailbox unreadMessages={messages} />,
  document.getElementById('root')
);

方法三:三元表达式

三元表达式:条件表达式 ? 表达式1 : 表达式2

如果条件表达式结果为真,则返回表达式1的值,为假,则返回表达式2的值

function UserGreeting(props) {
  return <h1>Welcome back!</h1>;
}
function GuestGreeting(props) {
  return <h1>Please sign up.</h1>;
}

function Greeting(props) {
  return {props.isLoggedIn ? <UserGreeting /> : <GuestGreeting />}
}

ReactDOM.render(
  <Greeting isLoggedIn={false} />,
  document.getElementById('root')
);

4 列表与表单

4.1 列表

在 React 中,把数组转化为元素列表的过程与JS中相似。

渲染基础列表组件

  • 使用 { } 在 JSX 内构建一个元素集合, 使用JS 中的map( )方法来遍历 numbers 数组。将数组中的每个元素变成标签。
  • key是在创建元素数组时,需要用到的一个特殊字符串属性。key 帮助 React 识别出被修改、添加或删除的 item。应当给数组内的每个元素都设定 key,以使元素具有固定身份标识。因此你应当给数组中的每一个元素赋予一个独一无二的标识。 通常我们使用数据中的 id 来作为元素的 key 。
function NumberList(props) {
  const numbers = props.numbers;
  return (
    <ul>
      {numbers.map((number) => //在 map()方法中的元素需要设置key属性
        <ListItem key={number.toString()} value={number} />
      )}
    </ul>
  );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);

4.2 表单

收集表单数据,包含表单元素的组件分为受控组件非受控组件

  • 受控组件: 页面中输入类的DOM (如input、textarea、select), 随着输入的过程,将数据存储在状态state中,需要用的时候在从状态state中取 (随时更新) ,比如在输入框输入用户和密码,随输入随展示数据 ,受控组件类似Vue中双向绑定( 数据变化更新视图,视图变化更新数据 )。
  • 非受控组件: 页面中输入类的DOM在有需求的时候才存储到状态中(现用现取),比如在输入框输入用户名和密码,点击登录触发回调才会获取数据

受控组件实例

// 创建组件
class Login extends React.Component {
  // 初始化状态
  state = {
    username: '',
    password: ''
  }
  // 保存用户名到状态中
  saveUsername = (event) => {
    this.setState({username: event.target.value})
  }
  // 保存密码到状态中
  savePassword = (event) => {
    this.setState({password: event.target.value})
  }
  // 表单提交的回调
  handleSubmit = (event) => {
    event.preventDefault()
    const {username, password} = this.state
    alert(`您输入的用户名是 ${username},您输入的密码是:${password}`)
  }

  render() {
    return (
      <form action="https://www.baidu.com/" onSubmit={this.handleSubmit}>
        用户名:<input onChange={this.saveUsername} type="text" name="username" />
        密码:<input onChange={this.savePassword} type="password" name="password" />
        <button>登录</button>  
      </form>
    )
  }
}
// 渲染组件
ReactDOM.render(<Login />, document.getElementById('test'))

在这里插入图片描述

非受控组件实例

// 创建组件
class Login extends React.Component {
  handleSubmit = (event) => {
    event.preventDefault() // 阻止表单提交
    const {username, password} = this
    alert(`您输入的用户名是 ${username.value},您输入的密码是:${password.value}`)
  }
  render() {
    return (
      <form action="https://www.baidu.com/" onSubmit={this.handleSubmit}>
        用户名:<input ref={c => this.username = c} type="text" name="username" />
        密码:<input ref={c => this.password = c} type="password" name="password" />
        <button>登录</button>  
      </form>
    )
  }
}
// 渲染组件
ReactDOM.render(<Login />, document.getElementById('test'))

在这里插入图片描述

5 状态提升与组合

5.1 状态提升

状态提升 :在 React 中,将多个组件中需要共享的 state 向上移动到它们的最近共同父组件中,React 的数据是自上而下的流动的,多个组件便可实现共享 state。这就是所谓的“状态提升”

概括: 子组件调用父组件方法改变父组件的state,从而改变子组件。(注:它本身或其它组件)

实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用

  • 一个组件在用:放在组件自身即可
  • 一些组件在用:放在他们共同的父组件上(状态提升)
  • 实现交互:从绑定事件开始
// 父组件
class Father extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      value: '',
    }
  }
  valueChange(value) {
    this.setState({
      value
    })
  }
  render() {
    return (
      <div style={{ padding: '100px' }}>
        {/*  传参 */}
        <Child1
          value={this.state.value}
          onValueChange={this.valueChange.bind(this)}
        />
        <br />
        <Child2 value={this.state.value} />
      </div>
    )
  }
}

// 子组件1
class Child1 extends React.Component {
  // 入参
  constructor(props) {
    super(props)
  }
  handle(e) {
    this.props.onValueChange(e.target.value)
  }
  render() {
    return <input value={this.props.value} onChange={this.handle.bind(this)} />
  }
}

// 子组件2
class Child2 extends React.Component {
  // 入参
  constructor(props) {
    super(props)
  }
  render() {
    return <input value={this.props.value} />
  }
}

ReactDOM.render(<Father />, document.getElementById('root'))

在这里插入图片描述

5.2 组合

**React官方推荐使用组合来实现组件间的代码重用, 组件可以接受任意 props,包括基本数据类型,React 元素以及函数。 **

包含关系

function FancyBorder(props) {
  return (
    //props包括属性和子组件children
    <div className={'FancyBorder FancyBorder-' + props.color}>
      {props.children}
    </div>
  );
}
function WelcomeDialog() {
  return (
    const color="blue"
      // 传参不仅可以传递属性(color="blue"常量 color={color}变量),还可以传递子组件children
    <FancyBorder color="blue" //color={color}>
      <h1 className="Dialog-title">
        Welcome
      </h1>
      <p className="Dialog-message">
        Thank you for visiting our spacecraft!
      </p>
    </FancyBorder>
  );
}

少数情况下,你可能需要在一个组件中预留出几个“洞”。这种情况下,我们可以不使用 children,而是自行约定:将所需内容传入 props,并使用相应的 prop。

<Contacts /> 和 和 <Chat /> 之类的 React 元素本质就是对象(object),所以你可以把它们当作 props,像其他数据一样传递。

function SplitPane(props) {
  return (
    <div className="SplitPane">
      <div className="SplitPane-left">
        {props.left}
      </div>
      <div className="SplitPane-right">
        {props.right}
      </div>
    </div>
  );
}

function App() {
  return (
    <SplitPane
      left={
        <Contacts />
      }
      right={
        <Chat />
      } />
  );
}

6 高阶函数、函数柯里化、高阶组件

6.1 高阶函数

高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。

  • 若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。
  • 若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。

常见的高阶函数有:PromisesetTimeoutarr.map()等等

6.2 函数的柯里化

函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。

function sum1(a, b, c){
  return a + b + c;
}
sum1(1, 2, 3)

// 柯里化后
function sum(a){
  return(b)=>{
	return (c)=>{
	  return a+b+c
	}
  }
}
sum(1)(2)(3)

6.3 高阶组件

什么是高阶函数?

在这里插入图片描述

什么是高阶组件?

在这里插入图片描述

高阶组件解决什么问题?

希望组件的去做一些事情,这件事情本身和组件没有关系 这时可以使用高阶组件 ,高阶组件的作用:解耦,增强复用性,现在高阶组件用的比较少了,写函数组件可以把功能单独抽出来使用
在这里插入图片描述

案例: 复用右下方悬浮按钮 ,此按钮和页面的UI逻辑无关,增加组件的复用性

// withFloatButton.tsx

import React, {useEffect} from 'react';
import {Image, StyleSheet, TouchableOpacity} from 'react-native';

type IReactComponent =
  | React.ClassicComponentClass
  | React.ComponentClass
  | React.FunctionComponent
  | React.ForwardRefExoticComponent<any>;

import icon_add from '../assets/images/icon_add.png';

export default <T extends IReactComponent>(OriginView: T, type: string): T => {
  const HOCView = (props: any) => {
    return (
      <>
        <OriginView {...props} />
        <TouchableOpacity
          style={styles.addButton}
          onPress={() => {
            console.log(`onPress ...`);
          }}>
          <Image style={styles.addImg} source={icon_add} />
        </TouchableOpacity>
      </>
    );
  };

  return HOCView as T;
};

const styles = StyleSheet.create({
  addButton: {
    position: 'absolute',
    bottom: 80,
    right: 28,
  },
  addImg: {
    width: 54,
    height: 54,
    resizeMode: 'contain',
  },
});

// InfoView.js

import React from 'react';
import {Image, StyleSheet, Text, View} from 'react-native';

import icon_avatar from '../assets/images/default_avatar.png';
import withFloatButton from './withFloatButton';

export default withFloatButton(() => {
  const styles = darkStyles;
  return (
    <View style={styles.content}>
      <Image style={styles.img} source={icon_avatar} />
      <Text style={styles.txt}>个人信息介绍</Text>
      <View style={styles.infoLayout}>
        <Text style={styles.infoTxt}>
          各位产品经理大家好,我是个人开发者张三,我学习RN两年半了,我喜欢安卓、RN、Flutter,Thank
          you!</Text>
      </View>
    </View>
  );
}, 'InfoView');

const darkStyles = StyleSheet.create({
  content: {
    width: '100%',
    height: '100%',
    backgroundColor: '#353535',
    flexDirection: 'column',
    alignItems: 'center',
    paddingHorizontal: 16,
    paddingTop: 64,
  },
  img: {
    width: 96,
    height: 96,
    borderRadius: 48,
    borderWidth: 4,
    borderColor: '#ffffffE0',
  },
  txt: {
    fontSize: 24,
    color: 'white',
    fontWeight: 'bold',
    marginTop: 32,
  },
  infoLayout: {
    width: '90%',
    padding: 16,
    backgroundColor: '#808080',
    borderRadius: 12,
    marginTop: 24,
  },
  infoTxt: {
    fontSize: 16,
    color: 'white',
  },
});

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值