MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

React 利用 Props 动态控制样式

2023-09-161.9k 阅读

React 利用 Props 动态控制样式

理解 React 中的 Props

在 React 开发中,props(properties 的缩写)是一种用于在组件之间传递数据的机制。它是单向流动的,即从父组件传递到子组件。通过 props,父组件可以向子组件传递各种类型的数据,包括字符串、数字、布尔值、对象、函数等。

Props 的基本使用

让我们先来看一个简单的例子,创建一个 Button 组件,通过 props 传递一个 text 属性来显示按钮的文本。

import React from 'react';

const Button = ({ text }) => {
  return <button>{text}</button>;
};

export default Button;

在父组件中使用这个 Button 组件:

import React from'react';
import Button from './Button';

const App = () => {
  return (
    <div>
      <Button text="点击我" />
    </div>
  );
};

export default App;

在这个例子中,App 组件作为父组件,将 text 属性传递给 Button 子组件,Button 组件通过解构赋值从 props 中获取 text 并显示在按钮上。

利用 Props 控制样式的基础

React 允许我们根据 props 的值来动态地改变组件的样式。这为我们创建高度可定制化的组件提供了很大的灵活性。

简单的样式切换

假设我们有一个 Box 组件,根据传递的 isHighlighted 属性来改变背景颜色。

import React from'react';

const Box = ({ isHighlighted }) => {
  const boxStyle = {
    width: '100px',
    height: '100px',
    backgroundColor: isHighlighted? 'yellow' : 'lightgray'
  };

  return <div style={boxStyle}></div>;
};

export default Box;

在父组件中使用:

import React from'react';
import Box from './Box';

const App = () => {
  const [isHighlighted, setIsHighlighted] = React.useState(false);

  const handleClick = () => {
    setIsHighlighted(!isHighlighted);
  };

  return (
    <div>
      <Box isHighlighted={isHighlighted} />
      <button onClick={handleClick}>切换高亮</button>
    </div>
  );
};

export default App;

在这个例子中,Box 组件根据 isHighlighted 属性的值来动态地设置背景颜色。父组件通过 useState 钩子来管理 isHighlighted 的状态,并通过按钮点击事件来切换这个状态。

复杂样式控制与类名操作

对于更复杂的样式控制,我们可能需要操作多个 CSS 类名。React 中可以借助第三方库如 classnames 来方便地管理动态类名,也可以手动进行类名拼接。

使用 classnames 库

首先安装 classnames

npm install classnames

然后来看一个例子,创建一个 Card 组件,根据不同的 props 来添加不同的类名。

import React from'react';
import classnames from 'classnames';

const Card = ({ isLarge, isSpecial }) => {
  const cardClassName = classnames('card', {
    'card-large': isLarge,
    'card-special': isSpecial
  });

  return <div className={cardClassName}></div>;
};

export default Card;

假设我们有如下 CSS 样式:

.card {
  border: 1px solid gray;
  padding: 10px;
}

.card-large {
  width: 300px;
  height: 200px;
}

.card-special {
  background-color: lightblue;
}

在父组件中使用:

import React from'react';
import Card from './Card';

const App = () => {
  return (
    <div>
      <Card isLarge={true} isSpecial={true} />
    </div>
  );
};

export default App;

在这个例子中,classnames 库根据 isLargeisSpecial 的布尔值来动态地添加或移除对应的类名,从而实现复杂样式的控制。

手动拼接类名

不使用第三方库时,我们也可以手动拼接类名。

import React from'react';

const Card = ({ isLarge, isSpecial }) => {
  let cardClassName = 'card';
  if (isLarge) {
    cardClassName +='card-large';
  }
  if (isSpecial) {
    cardClassName +='card-special';
  }

  return <div className={cardClassName}></div>;
};

export default Card;

这种方式虽然没有 classnames 库那么简洁,但对于简单的场景也能满足需求。

基于 Props 的响应式样式

随着移动设备的普及,响应式设计变得至关重要。在 React 中,我们可以利用 props 来实现响应式样式。

根据屏幕尺寸切换样式

假设我们要根据屏幕宽度来显示不同样式的导航栏。

import React, { useEffect, useState } from'react';

const Navbar = ({ isMobile }) => {
  const navStyle = {
    backgroundColor: isMobile? 'lightgray' : 'darkgray',
    color: isMobile? 'black' : 'white'
  };

  return <nav style={navStyle}></nav>;
};

const App = () => {
  const [isMobile, setIsMobile] = useState(false);

  useEffect(() => {
    const handleResize = () => {
      setIsMobile(window.innerWidth < 768);
    };

    window.addEventListener('resize', handleResize);
    handleResize();

    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []);

  return (
    <div>
      <Navbar isMobile={isMobile} />
    </div>
  );
};

export default App;

在这个例子中,App 组件通过 useEffect 钩子监听窗口大小变化,根据屏幕宽度是否小于 768px 来设置 isMobile 状态,并将其传递给 Navbar 组件,Navbar 组件根据 isMobile 的值来应用不同的样式。

利用 Props 控制内联样式的细节

虽然内联样式在 React 中很方便,但也有一些细节需要注意。

样式属性的命名

在 React 中,内联样式的属性名采用驼峰命名法,而不是 CSS 中的连字符命名法。例如,CSS 中的 background-color 在 React 内联样式中写作 backgroundColor

import React from'react';

const Box = () => {
  const boxStyle = {
    backgroundColor: 'lightblue',
    fontSize: '16px'
  };

  return <div style={boxStyle}></div>;
};

export default Box;

样式值的类型

内联样式的值通常是字符串,但对于一些数值类型的属性,如 widthheight 等,可以直接使用数字,React 会自动添加 px 单位。

import React from'react';

const Box = () => {
  const boxStyle = {
    width: 200,
    height: 150
  };

  return <div style={boxStyle}></div>;
};

export default Box;

然而,如果要使用其他单位,如 emrem 等,则需要使用字符串形式。

import React from'react';

const Box = () => {
  const boxStyle = {
    fontSize: '1.2em'
  };

  return <div style={boxStyle}></div>;
};

export default Box;

传递样式对象作为 Props

有时候,将整个样式对象作为 props 传递会更加方便,特别是当多个组件需要共享相同的样式逻辑时。

创建共享样式对象

假设我们有一个 Text 组件和一个 Title 组件,它们都需要一些共同的样式。

import React from'react';

const sharedTextStyle = {
  fontFamily: 'Arial, sans - serif',
  color: 'darkblue'
};

const Text = ({ text }) => {
  return <p style={sharedTextStyle}>{text}</p>;
};

const Title = ({ title }) => {
  return <h1 style={sharedTextStyle}>{title}</h1>;
};

export { Text, Title };

在父组件中使用:

import React from'react';
import { Text, Title } from './TextAndTitle';

const App = () => {
  return (
    <div>
      <Title title="文章标题" />
      <Text text="文章内容" />
    </div>
  );
};

export default App;

通过这种方式,我们可以统一管理和更新共享的样式,提高代码的可维护性。

动态样式与性能优化

当频繁地根据 props 改变样式时,性能问题可能会出现。React 的虚拟 DOM 机制会尽量高效地更新真实 DOM,但我们也可以采取一些措施来进一步优化。

避免不必要的样式计算

确保在计算样式时,只依赖于 props 中真正影响样式的值。例如,如果一个组件的背景颜色只依赖于 isHighlighted 属性,而不依赖于其他无关的 props,那么在计算背景颜色样式时,只考虑 isHighlighted

import React from'react';

const Box = ({ isHighlighted, otherProp }) => {
  const boxStyle = {
    width: '100px',
    height: '100px',
    backgroundColor: isHighlighted? 'yellow' : 'lightgray'
    // 不依赖 otherProp,避免因 otherProp 变化导致不必要的样式计算
  };

  return <div style={boxStyle}></div>;
};

export default Box;

使用 React.memo

对于纯展示组件,即只根据 props 渲染,不依赖于内部状态的组件,可以使用 React.memo 来包裹组件,这样当 props 没有变化时,组件不会重新渲染,从而提高性能。

import React from'react';

const Box = ({ isHighlighted }) => {
  const boxStyle = {
    width: '100px',
    height: '100px',
    backgroundColor: isHighlighted? 'yellow' : 'lightgray'
  };

  return <div style={boxStyle}></div>;
};

export default React.memo(Box);

与 CSS - in - JS 库结合使用

除了传统的 CSS 和内联样式,还有一些 CSS - in - JS 库,如 styled - componentsemotion,可以与 React 结合使用,利用 props 来动态控制样式。

使用 styled - components

首先安装 styled - components

npm install styled - components

然后创建一个 Button 组件,根据 props 改变样式。

import React from'react';
import styled from'styled - components';

const StyledButton = styled.button`
  background - color: ${props => props.primary? 'blue' : 'gray'};
  color: white;
  padding: 10px 20px;
  border: none;
  border - radius: 5px;
`;

const Button = ({ primary, text }) => {
  return <StyledButton primary={primary}>{text}</StyledButton>;
};

export default Button;

在父组件中使用:

import React from'react';
import Button from './Button';

const App = () => {
  return (
    <div>
      <Button primary={true} text="主要按钮" />
      <Button primary={false} text="次要按钮" />
    </div>
  );
};

export default App;

在这个例子中,styled - components 使用模板字符串来定义样式,通过 props 来动态设置 background - color

使用 emotion

安装 emotion

npm install @emotion/react @emotion/styled

示例代码如下:

import React from'react';
import { styled } from '@emotion/react';

const StyledBox = styled.div`
  width: 100px;
  height: 100px;
  background - color: ${props => props.isHighlighted? 'yellow' : 'lightgray'};
`;

const Box = ({ isHighlighted }) => {
  return <StyledBox isHighlighted={isHighlighted}></StyledBox>;
};

export default Box;

这些 CSS - in - JS 库提供了一种更加直观和便捷的方式来根据 props 控制样式,同时还能享受作用域样式等优点。

跨组件样式传递与管理

在大型应用中,可能需要在多个层级的组件之间传递样式相关的 props。这时候,合理的组件设计和状态管理就变得很重要。

多层级组件传递 Props

假设我们有一个 App 组件,它包含一个 Container 组件,Container 组件又包含一个 Box 组件,我们要从 App 组件传递一个 isHighlighted 属性到 Box 组件来控制其样式。

import React from'react';

const Box = ({ isHighlighted }) => {
  const boxStyle = {
    width: '100px',
    height: '100px',
    backgroundColor: isHighlighted? 'yellow' : 'lightgray'
  };

  return <div style={boxStyle}></div>;
};

const Container = ({ isHighlighted }) => {
  return (
    <div>
      <Box isHighlighted={isHighlighted} />
    </div>
  );
};

const App = () => {
  const [isHighlighted, setIsHighlighted] = React.useState(false);

  const handleClick = () => {
    setIsHighlighted(!isHighlighted);
  };

  return (
    <div>
      <Container isHighlighted={isHighlighted} />
      <button onClick={handleClick}>切换高亮</button>
    </div>
  );
};

export default App;

在这个例子中,App 组件将 isHighlighted 传递给 Container 组件,Container 组件再将其传递给 Box 组件。这种层层传递的方式在组件层级较深时可能会变得繁琐。

使用 Context 进行样式管理

为了避免层层传递 props,可以使用 React 的 Context API。假设我们要创建一个全局的主题样式,通过 Context 来传递主题相关的 props

首先创建 Context:

import React from'react';

const ThemeContext = React.createContext();

export default ThemeContext;

然后创建一个主题提供组件:

import React from'react';
import ThemeContext from './ThemeContext';

const ThemeProvider = ({ children }) => {
  const theme = {
    primaryColor: 'blue',
    secondaryColor: 'gray'
  };

  return (
    <ThemeContext.Provider value={theme}>
      {children}
    </ThemeContext.Provider>
  );
};

export default ThemeProvider;

接着在需要使用主题的组件中消费 Context:

import React from'react';
import ThemeContext from './ThemeContext';

const Box = () => {
  const theme = React.useContext(ThemeContext);

  const boxStyle = {
    width: '100px',
    height: '100px',
    backgroundColor: theme.primaryColor
  };

  return <div style={boxStyle}></div>;
};

export default Box;

在父组件中使用:

import React from'react';
import ThemeProvider from './ThemeProvider';
import Box from './Box';

const App = () => {
  return (
    <ThemeProvider>
      <Box />
    </ThemeProvider>
  );
};

export default App;

通过 Context,我们可以在组件树的任意层级获取主题相关的样式信息,而无需层层传递 props

实践中的样式控制案例

创建一个可定制的卡片组件

假设我们要创建一个卡片组件,它可以根据不同的 props 来定制样式,包括卡片的大小、颜色、是否有边框等。

import React from'react';
import classnames from 'classnames';

const Card = ({ size, color, hasBorder }) => {
  const cardClassName = classnames('card', {
    'card - small': size ==='small',
    'card - medium': size ==='medium',
    'card - large': size === 'large',
    'card - red': color ==='red',
    'card - blue': color === 'blue',
    'card - with - border': hasBorder
  });

  return <div className={cardClassName}></div>;
};

export default Card;

CSS 样式如下:

.card {
  padding: 10px;
}

.card - small {
  width: 150px;
  height: 100px;
}

.card - medium {
  width: 250px;
  height: 150px;
}

.card - large {
  width: 350px;
  height: 200px;
}

.card - red {
  background - color: lightcoral;
}

.card - blue {
  background - color: lightblue;
}

.card - with - border {
  border: 1px solid gray;
}

在父组件中使用:

import React from'react';
import Card from './Card';

const App = () => {
  return (
    <div>
      <Card size="medium" color="blue" hasBorder={true} />
    </div>
  );
};

export default App;

实现一个响应式菜单组件

import React, { useEffect, useState } from'react';

const Menu = ({ isMobile }) => {
  const menuStyle = {
    display: isMobile? 'block' : 'flex',
    flexDirection: isMobile? 'column' : 'row',
    backgroundColor: isMobile? 'lightgray' : 'darkgray',
    color: isMobile? 'black' : 'white'
  };

  return <nav style={menuStyle}></nav>;
};

const App = () => {
  const [isMobile, setIsMobile] = useState(false);

  useEffect(() => {
    const handleResize = () => {
      setIsMobile(window.innerWidth < 768);
    };

    window.addEventListener('resize', handleResize);
    handleResize();

    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []);

  return (
    <div>
      <Menu isMobile={isMobile} />
    </div>
  );
};

export default App;

在这个例子中,Menu 组件根据 isMobile 属性来实现响应式布局,当屏幕宽度小于 768px 时,菜单以单列显示,背景颜色和文本颜色也相应改变。

通过以上多种方式,我们可以在 React 中灵活且高效地利用 props 来动态控制样式,满足各种不同的业务需求和设计要求。无论是简单的样式切换还是复杂的响应式布局和跨组件样式管理,都可以通过合理运用 props 以及相关的技术手段来实现。在实际开发中,需要根据项目的规模、复杂度以及性能要求等因素,选择最合适的方式来进行样式控制。同时,要注意代码的可维护性和可扩展性,以便在项目的后续迭代中能够轻松地对样式逻辑进行修改和优化。