函数组件:使用JS的函数(或箭头函数)创建的组件称为函数组件,函数组件有如下约定
函数名称必须以大写字母开头
函数组件必须有返回值,返回JSX表达式
渲染函数组件:用函数名作为组件标签名
组件名称可以是单标签也可以是双标签
- export default function Hello(){
- return (
- <div>
- Hello Function Component
- div>
- )
- }
VSCode 中快捷键 rfc
可自动生成。
创建完函数组件,可以将其引入到App.jsx中
- import './App.css'
- import Hello from './Hello';
- function App() {
- return (
- <div>
- <Hello>Hello>
- div>
- );
- }
- export default App;
注意:函数组件中绑定方法不需要this
- export default function Hello() {
- const clickEvent = () => {
- console.log('2');
- }
- const getParams = (params) => {
- console.log(params);
- }
- return (
- <>
- <button onClick={() => {
- console.log('1');
- }}>按钮button>
- <button onClick={clickEvent}>按钮button>
- <button onClick={() => getParams('3')}>传参button>
- >
- )
- }
- import React from 'react';
- import ReactDOM from 'react-dom';
- function LoginForm(props){
- return <h1>{props.title}登录h1>;
- }
- const template=<LoginForm title='管理员'>LoginForm>
- ReactDOM.render(template,document.getElementById('root'));
当然也可以自己在调用函数组件的时候指定其他的,比如下面这个例子输入会员,就会变成会员登录
- import React from 'react';
- import ReactDOM from 'react-dom';
- function LoginForm(props){
- return <h1>{props.title}登录h1>;
- }
- const template=<LoginForm title='会员'>LoginForm>
- ReactDOM.render(template,document.getElementById('root'));
下面我们再定义一个函数式组件,然后在这里边传递多个参数
- import React from 'react';
- import ReactDOM from 'react-dom';
-
- function UserInfo(props){
- console.log(props);
- const template=(
- <div>
- <ul>
- <li>姓名:{props.username}li>
- <li>年龄:{props.age}li>
- <li>特长:{props.specialty}li>
- ul>
- div>
- );
- return template;
- }
- const template=(
- <div>
- <UserInfo username={'马保国'} age={56} specialty={'太极'}>UserInfo>
- div>
- );
- ReactDOM.render(template,document.getElementById('root'));
显示效果如下所示,我们通过对控制台的观察,会发现props就是传递的JS对象的值
如上给组件赋值除了赋常量外也可以传递变量,如下所示
- import React from 'react';
- import ReactDOM from 'react-dom';
-
- function UserInfo(props){
- const template=(
- <div>
- <ul>
- <li>姓名:{props.username}li>
- <li>年龄:{props.age}li>
- <li>特长:{props.specialty}li>
- ul>
- div>
- );
- return template;
- }
- const u={
- username:'马保国',
- age:69,
- specialty:'打太极'
- };
- const template=(
- <div>
- <UserInfo username={u.username} age={u.age} specialty={u.specialty}>UserInfo>
- div>
- );
- ReactDOM.render(template,document.getElementById('root'));
其实还有简写方式
- import React from 'react';
- import ReactDOM from 'react-dom';
-
- function UserInfo(props){
- const template=(
- <div>
- <ul>
- <li>姓名:{props.username}li>
- <li>年龄:{props.age}li>
- <li>特长:{props.specialty}li>
- ul>
- div>
- );
- return template;
- }
- const u={
- username:'马保国',
- age:69,
- specialty:'打太极'
- };
- const template=(
- <div>
- <UserInfo {...u}>UserInfo>
- div>
- );
- ReactDOM.render(template,document.getElementById('root'));
首先,先来完成一个函数组件的默认值案例
- import React from 'react';
- import ReactDOM from 'react-dom';
-
- function UserInfo(props){
- const template=(
- <div>
- <ul>
- <li>姓名:{props.username}li>
- <li>年龄:{props.age}li>
- <li>特长:{props.specialty}li>
- ul>
- div>
- );
- return template;
- }
- //设置默认值的语法 组件名.defaultProps
- UserInfo.defaultProps={
- username:'无名氏',
- age:0,
- specialty:'无特长'
- }
- const template=(
- <div>
- <UserInfo username='张明理'>UserInfo>
- div>
- );
- ReactDOM.render(template,document.getElementById('root'));
这里如果在调用组件的时候赋值,那么默认值就不会生效了,倘若,默认值就会生效。
- import React from 'react';
- import ReactDOM from 'react-dom';
- import PropTypes from 'prop-types';
-
- function UserInfo(props){
- const template=(
- <div>
- <ul>
- <li>姓名:{props.username}li>
- <li>年龄:{props.age}li>
- <li>特长:{props.specialty}li>
- ul>
- div>
- );
- return template;
- }
- UserInfo.propTypes={
- username:PropTypes.string.isRequired,
- age:PropTypes.number.isRequired,
- specialty:PropTypes.string
- }
- const template=(
- <div>
- <UserInfo age={'20岁'} specialty='打羽毛球'>UserInfo>
- div>
- );
- ReactDOM.render(template,document.getElementById('root'));
函数组件又叫做无状态组件,类组件又叫做有状态组件
状态(state)即数据,是组件内部的私有数据,只能在组件内部使用
函数组件没有自己的状态,只负责数据展示
类组件有自己的状态,负责更新UI
react16.8以后的新特性Hooks函数组件在react16.8以前函数组件只能被动接收外部数据,并且没有自己的生命周期钩子函数,函数内部也没有this可用新特性Hookhook推出的动机主要是因为类组件有一下几个不足
组件之间复用公共逻辑比较麻烦,以前常见的提取组件公共逻辑的方式有高阶组件/renderProps等,但这些方式或多或少都对原有组件的代码组织方式有一定的破坏性
复杂组件变得难以理解(例如相同的逻辑可能要写在不同的生命周期钩子函数里面)
难以理解的class(比如对新手来见,class组件中的this不太好理解)
新特性hook出现之后,函数组件就可以完全替代类组件,但是这并不是说react官方会抛弃类组件,react官方文档中也表明了没有计划会在react中移除class组件。
注意:hook特性只能在函数组件中使用
useState 为函数组件提供了状态(state)
导入useState
函数
import { useState } from 'react'
调用useState
函数,并传入状态的初始值,从useState
函数的返回值中,拿到状态和修改状态的方法
const [变量名, 方法名] = useState(数据初始值);
在jsx中展示状态
使用修改状态的方法更新状态
- import { useState } from 'react'
-
- export default function Counter() {
- const [count, setCount] = useState(0);
- const decrement = () => {
- setCount(count - 1)
- }
- return (
- <>
- <h1>计数器:{count}h1>
- <button onClick={decrement}>-1button>
- <button onClick={() => {setCount(count + 1)}}>+1button>
- >
- )
- }
注意:修改数据的 set 方法,只需要接收一个新数据作为参数,方法内部会自动用接收到的新数据来覆盖旧数据。
语法
const [变量名, 方法名]=useState(()=>{return '计算之后的初始值'})
语法规则
回调函数return出去的值作为变量名
的初始值
回调函数中的逻辑只会在组件初始化的时候执行一次
语法选择
如果初始化一个普通的数据,直接使用useState(初始值)
即可
如果要初始化的数据无法直接得到需要通过计算才能获取到,使用useState(()=>{})
案例实现
- import {useState} from 'react'
- function Counter(props){
- const [count,setCount]=useState(()=>{
- return props.count
- })
- return(
- <>
- <h2>{count}h2>
- <button onClick={()=>setCount(count+1)}>+button>
- >
- )
- }
- function App() {
- return(
- <>
- <Counter count={10}>Counter>
- <Counter count={20}>Counter>
- >
- )
- }
- export default App;
副作用:副作用是相对与主作用而言的,一个函数除了主作用,其他作用都是副作用,对于React组件来说,主作用是根据数据(state/props)渲染UI,除此之外都是副作用
常见的副作用
数据请求Ajax发送
手动修改DOM
localStorage操作
Effect Hook 可以让你在函数组件中执行副作用操作
- import {useState,useEffect} from 'react'
-
- export default function Example() {
- const [count,setCount]=useState(0)
- useEffect(()=>{
- document.title=`You clicked ${count} times`
- })
- return (
- <div>
- <p>You clicked {count} timesp>
- <button onClick={()=>{setCount(count+1)}}>Click Mebutton>
- div>
- )
- }
- import { useEffect } from 'react'
- useEffect(() => {}, []);
useEffect 的参数,主要分为两个,第一个固定是一个回调函数,第二个是一个数组(可选)。
参数传递的不同,useEffect 实现的效果也不同。
当 useEffect 只有第一个参数时,其作用就是在模拟 componentDidMount
和 componentDidUpdate
生命周期函数。
也就是说,useEffect 的第一个回调函数,会在组件首次挂载完成执行一次,同时,后续组件每次更新完成时也会执行。
- useEffect(() => {
- // ...
- })
关键代码如下所示
- import {useState,useEffect} from 'react'
-
- export default function Params() {
- const [count,setCount]=useState(0)
- useEffect(()=>{
- console.log('没有第2个参数');
- })
- return (
- <div>
- <h1>{count}h1>
- <button onClick={()=>setCount(count+1)}>+1button>
- div>
- )
- }
执行的效果是:组件首次挂载完成执行一次,每次单击按钮会执行一次
当 useEffect 的第二个参数是一个空数组是,其作用就是在模拟 componentDidMount
生命周期函数。
也就是说,useEffect 的第一个回调函数,会在组件首次挂载完成执行一次。
- useEffect(() => {
- // ...
- }, [])
关键代码如下所示
- import {useState,useEffect} from 'react'
-
- export default function Params() {
- const [count,setCount]=useState(0)
- useEffect(()=>{
- console.log('没有第2个参数');
- },[])
- return (
- <div>
- <h1>{count}h1>
- <button onClick={()=>setCount(count+1)}>+1button>
- div>
- )
- }
执行的效果是:只有在组件首次挂载完成执行一次,后续单击不会执行
当 useEffect 的第二个参数是一个非空数组时,其作用就是在模拟 componentDidMount
生命周期函数,同时还可以模拟 Vue 中 watch 的作用。
也就是说,useEffect 的第一个回调函数,会在组件首次挂载完成执行一次。后续,只要当第二个参数的数组中,任意一条数据发生改变,useEffect 的第一个回调函数又会再次执行。
- useEffect(() => {
- // ...
- }, [数据1, 数据2, ...])
关键代码如下所示
- import {useState,useEffect} from 'react'
-
- export default function Params() {
- const [count,setCount]=useState(0)
- const [name,setName]=useState('张三')
- useEffect(()=>{
- console.log('第2个参数是非空数字');
- },[count])
- return (
- <div>
- <h1>{count}h1>
- <h1>{name}h1>
- <button onClick={()=>setCount(count+1)}>+1button>
- <button onClick={()=>setName('李四')}>更新button>
- div>
- )
- }
执行的结果是:在组件首次挂载完成执行一次,后续只有在点击+1操作按钮的时候才会调用useEffect的回调函数。单击更新按钮不会触发useEffect回调函数的执行
当 useEffect 的第一个参数中,返回了一个函数。那么,返回的这个函数就是在模拟 componentWillUnmount
生命周期函数。
在组件被销毁时,如果有些副作用操作需要被清理,在这里可以写清理副作用的代码。
- useEffect(() => {
- return () => {
-
- }
- })
关键代码
- import { useEffect } from 'react'
-
- export default function ClearEffect() {
- useEffect(() => {
- let times=setInterval(() => {
- console.log('定时执行操作');
- }, 1000);
- return ()=>{
- clearInterval(times)
- }
- }, [])
- return (
- <div>this is testdiv>
- )
- }
- ***************************************************************************************
- import ClearEffect from "./ClearEffect"
- import { useState } from "react"
- export default function App() {
- const [flag,setFlag]=useState(true)
- return (
- <div>
- {flag? <ClearEffect/>:null}
- <button onClick={()=>setFlag(!flag)}>switchbutton>
- div>
- )
- }
- import axios from 'axios'
- import React, { useState, useEffect } from 'react'
-
- export default function User() {
- const [users, setUsers] = useState([])
- const getUsersApi = async () => {
- let result = await axios.get('http://39.106.34.185:8888/user')
- setUsers(result.data.data.list)
- }
- useEffect(() => {
- getUsersApi()
- }, [])
- return (
- <div>
- <table>
- <tr>
- <td>昵称td>
- <td>手机号td>
- <td>绑定汽车数量td>
- <td>订单数量td>
- <td>未缴费订单数量td>
- <td>状态td>
- <td>创建事件td>
- <td>操作td>
- tr>
- {
- users.map(item => <tr key={item.id}>
- <td>{item.name}td>
- <td>{item.tel}td>
- <td>{item.carNum}td>
- <td>{item.orderNum}td>
- <td>{item.orderNum}td>
- <td>{item.state==1?'启用':'禁用'}td>
- <td>{item.createTime}td>
- <td>
- <span>查看span>
- <span>订单详情span>
- <span>更多span>
- td>
- tr>)
- }
- table>
- div>
- )
- }
在函数组件中获取DOM元素或者组件对象
导入useRef
函数
import { useRef } from 'react'
执行useRef
函数,返回一个ref对象
- function 组件名() {
- const 变量名 = useRef();
- }
绑定 ref
找到函数组件中对应的节点,通过 ref
属性将 ref 对象绑定到元素节点身上:
- function 组件名() {
- const 变量名 = useRef();
- return (
- <节点 ref={变量名}>节点>
- )
- }
获取元素节点
当元素通过 ref
绑定了元素后,就可以通过 ref 对象来获取元素节点了:
- function 组件名() {
- const 变量名 = useRef();
- return (
- <节点 ref={变量名} onClick={() => {
- console.log(变量名.current); // 元素节点
- }}>节点>
- )
- }
- import React,{useRef} from 'react'
- import './Ball.css'
- export default function Ball() {
- const ballRf=useRef()
- const changeBall=()=>{
- ballRf.current.style.backgroundColor="springgreen"
- }
- return (
- <div>
- <div className="box" ref={ballRf}>div>
- <button onClick={changeBall}>变化button>
- div>
- )
- }
useMemo的作用类似vue中的计算属性computed,即根据母体数据衍生出新的数据,一旦母体数据发生变化,则useMemo会自动执行
- import { useMemo } from 'react'
-
- const 变量名 = useMemo(() => {
- return 计算得到的数据;
- }, [依赖的原数据])
最终,计算得到的数据,就会通过 return 保存到变量身上。
关键代码如下所示
- import {useState,useMemo} from 'react'
-
- export default function App() {
- const [fnum,setFnum]=useState(10)
- const [snum,setSnum]=useState(20)
- const sum=useMemo(()=>{
- return fnum+snum
- },[fnum,snum])
- return (
- <div>
- <h1>{sum}h1>
- <button onClick={()=>setFnum(fnum+2)}>fnum+2button>
- <button onClick={()=>setSnum(snum+3)}>snum+3button>
- div>
- )
- }
- import axios from 'axios'
- import React, { useState, useEffect,useMemo } from 'react'
- import './ShopCartList.css'
-
- export default function ShopCartList() {
- const [list, setList] = useState([])
- const fetchShopCartData = async () => {
- const result = await
- axios.get('http://www.zhaijizhe.cn:3001/shopcart/getShopcartData')
- console.log(result.data);
- setList(result.data.data)
- }
- const total=useMemo(()=>{
- return list.filter(item=>item.checked).reduce((pre,cur)=>pre+cur.price*cur.num,0)
- },[list])
- const changeNum=async(_id,n)=>{
- let result=await
- axios.post('http://www.zhaijizhe.cn:3001/shopcart/changeNum',{_id,n})
- if(result.data.code){
- fetchShopCartData()
- }
- }
- const checkItem=async(_id)=>{
- let result=await
- axios.get('http://www.zhaijizhe.cn:3001/shopcart/checkProducts',{params:{_id}})
- if(result.data.code){
- fetchShopCartData()
- }
- }
- const changeNum1=(_id,index,n)=>{
- list[index].num=n
- setList(()=>{
- return[
- ...list
- ]
- })
- }
- const submitChangeNum=async(_id,n)=>{
- let result=await
- axios.post('http://www.zhaijizhe.cn:3001/shopcart/changeNumByInput',{_id,n})
- if(result.data.code){
- fetchShopCartData()
- }
- }
-
- useEffect(() => {
- fetchShopCartData()
- }, [])
-
- return (
- <div>
- <h1>购物车h1>
- <table>
- <thead>
- <tr>
- <th>序号th>
- <th>名称th>
- <th>价格th>
- <th>数量th>
- <th>小计th>
- tr>
- thead>
- <tbody>
- {
- list.map((item,index)=><tr key={item._id}>
- <td>
- <input type='checkbox'
- onChange={()=>checkItem(item._id)}
- checked={item.checked}/>
- td>
- <td>{item.name}td>
- <td>{item.price}td>
- <td>
- <button onClick={()=>changeNum(item._id,-1)}>-button>
- <input type="text" value= {item.num}
- onChange={(e)=>
- {changeNum1(item._id,index,e.target.value)}}
- onBlur={(e)=>
- {submitChangeNum(item._id,e.target.value)}}/>
- <button onClick={()=>changeNum(item._id,1)}>+button>
- td>
- <td>{item.price*item.num}td>
- tr>)
- }
- <tr>
- <td colSpan={5} style={{textAlign:'right'}}>
- 总计:{total}
- td>
- tr>
- tbody>
- table>
- div>
- )
- }
React.memo 可以用来缓存组件。
在 React 中,默认情况下,父组件更新时,无论子组件内部是否改变,子组件也会更新。
但是,从性能考虑,我们希望的是:父组件更新时,如果子组件内部(state 和 props)没有发生改变,子组件不用更新。
解决方法,就是用 React.memo
将子组件缓存起来。
- import {useState} from 'react'
- import HookChild from './HookChild'
-
- export default function HookParent() {
- console.log('父组件');
- const [count,setCount]=useState(0)
- return (
- <div>
- <h1>父组件:{count}h1>
- <button onClick={()=>setCount(count+1)}>+1button>
- <HookChild>HookChild>
- div>
- )
- }
- import React from 'react'
-
- function HookChild() {
- console.log('子组件');
- return (
- <h2>子组件h2>
- )
- }
-
- export default React.memo(HookChild);
useCallback 可以用来缓存函数。
- import { useCallback } from 'react';
-
- const 变量名 = useCallback(缓存的函数, [])
通常,我们为了减少子组件不必要的更新,会使用 React.memo()
来缓存子组件的状态。
但是,如果父组件传递了函数给子组件,那么,每次父组件更新时,函数都会重新创建。子组件中的 React.memo()
就会判定为父组件传递的内容发生了改变,那么 React.memo()
就不会缓存当前子组件的状态。
因此,为了解决函数的问题,我们需要保存函数的状态,让父组件更新时函数能够缓存下来,因此我们需要使用 useCallback
将传递的子组件的函数状态缓存起来。
- import { useCallback, useState } from "react";
- import HookChild from "./HookChild";
-
- export default function HookFather() {
- console.log('父组件');
- const [count, setCount] = useState(0);
-
- const sayHello = () => {
- console.log('hello');
- }
- // 缓存函数状态
- const cbSayHello = useCallback(sayHello, []);
-
- return (
- <>
- <h1>父组件:{count}h1>
- <button onClick={() => setCount(count + 1)}>+1button>
- <HookChild sayHello={cbSayHello}>HookChild>
- >
- )
- }
- import React from 'react'
- function HookChild({sayHello}) {
- console.log('子组件');
- return (
- <div>
- <h2>子组件h2>
- div>
- )
- }
- export default React.memo(HookChild)
自定义hook提取函数组件公共逻辑
自定义hook的作用在于提取公共逻辑,所以一般不会返回一个JSX对象,而是会根据需要返回特定的数据或者方法
自定义hook必须要以use开头
假设现在有Test1和Test2两个函数组件,他们有一些公共逻辑需要提取,比如都要从后台获取一个列表数据,然后用户界面的渲染,则现在可以编写一个自定义hook来提取这部分公共逻辑,以避免重复写代码
- import {useState,useEffect} from 'react'
- export default function useData(){
- const [list,setList] = useState([]);
- useEffect(()=>{setTimeout(()=>{
- const list = [1,2,3]
- setList(list);
- },200);},[]);
- return {list,}}
- //Test1
- import useData from '...'
- function Test1(){
- const {list} = useData();
- return
- div>
- {...用list渲染JSX}
-
在vue中可以使用scoped对组件的样式进行私有化,在React中一样,React的样式私有化也有几种处理方法
内联样式就是在JSX元素中,直接定义行内的样式
- import React, { Component } from 'react'
- export default class Left extends Component {
- render() {
- return (
- <div style={{
- backgroundColor:'indigo',
- padding:20,
- color:'whitesmoke'
- }}>Leftdiv>
- )
- }
- }
- import React, { Component } from 'react'
- export default class Right extends Component {
- render() {
- return (
- <div style={{
- backgroundColor:'springgreen',
- padding:10,
- color:'black'
- }}>Rightdiv>
- )
- }
- }
内联样式的优点
使用简单:简单的以组件为中心来实现样式的添加
扩展方便:通过使用对象进行样式设置,可以方便的扩展对象来扩展样式
避免冲突:最终都编译为元素的行内样式,不存在样式冲突的问题
内联样式的缺点
不能使用伪类
不能使用媒体查询
减低代码的可读性
没有代码提示
CSS的规则都是全局的,任何一个组件的样式规则,都对整个页面有效,产生局部作用域的唯一方法,就是使用一个独一无二的class名称,这就是CSS Modules的做法,具体使用步骤如下
定义xxx.module.css文件
导入样式import xy from './xy.module.css'
使用{xy.className}来对元素进行样式修饰
- import React, { Component } from 'react'
- import left from './left.module.css'
- export default class Left extends Component {
- render() {
- return (
- <div className={left.box}>Leftdiv>
- )
- }
- }
注意:react-jss必须使用在函数组件中
使用步骤
下载react-jss依赖包
yarn add react-jss
调用createUseStyles完成样式的定义
- import {createUseStyles} from 'react-jss'
- const useStyles=createUseStyles({
- box:{
- backgroundColor:'lightblue',
- padding:'10px'
- }
- })
- export default function Left() {
- const {box}=useStyles()
- return (
- <div className={box}>Rightdiv>
- )
- }