🔴 你老说拿下 react,真的 拿下 了吗

🔴 jsx

⭕ JSX语法特点总结

1. 标签不需要引号

在JSX中写标签时不需要加引号,直接使用即可。

jsx 复制代码
// 正确
const element = <div>Hello</div>;

// 错误
const element = "<div>Hello</div>";

2. 花括号内可嵌入表达式

花括号{}内可以嵌入变量和表达式,但必须是能产生值的表达式。

jsx 复制代码
let age = 20;

// 嵌入变量
<p>{age}</p>

// 嵌入表达式
<p>{age + 1}</p>  // 显示21

// 三元表达式
<p>{age > 18 ? "成年人" : "青少年"}</p>

3. 表达式与语句的区别

  • 表达式 :产生值,如1+1age > 18 ? "成年人" : "青少年"
  • 语句 :不产生值,如if...elsefor循环、console.log()

JSX中只能嵌入表达式,不能嵌入语句。

4. 标签必须闭合

所有标签都必须正确闭合,包括自闭合标签。

jsx 复制代码
// 正确
<input />
<br />

// 错误
<input>
<br>

5. 最外层只能有一个元素

JSX表达式最外层必须有且只有一个根元素。

jsx 复制代码
// 正确
<div>
  <p>第一段</p>
  <p>第二段</p>
</div>

// 错误
<p>第一段</p>
<p>第二段</p>

6. 注释写法

JSX中的注释需要写在花括号内。

jsx 复制代码
{/* 这是一个注释 */}
<div>
  {/* 显示当前时间戳 */}
  {new Date().getTime()}
</div>

7. 样式处理

类名使用className
jsx 复制代码
// JSX
<p className="active">红色文字</p>

// 对应HTML
<p class="active">红色文字</p>
内联样式使用对象
jsx 复制代码
const style = {
  color: 'blue',
  backgroundColor: 'gray'
};

<p style={style}>带样式的文字</p>

等价于原生HTML:

html 复制代码
<p style="color: blue; background-color: gray;">带样式的文字</p>

JSX语法提供了将JavaScript和HTML混合编写的能力,但需要注意:

  1. 只能使用表达式,不能使用语句
  2. 标签必须正确闭合
  3. 样式处理方式与原生HTML有所不同
  4. 注释需要特殊写法

🔴 遍历数据

⭕ 条件渲染 - 短路运算

1. 短路运算原理

在JavaScript中,逻辑运算符&&||具有短路特性:

javascript 复制代码
// 与运算(&&)
true && expression  // 会执行expression
false && expression // 不会执行expression,直接返回false

// 或运算(||)
true || expression  // 不会执行expression,直接返回true
false || expression // 会执行expression

2. React中的条件渲染应用

jsx 复制代码
// 使用短路运算实现条件渲染
function App() {
  const isLoggedIn = true; // 从状态或props获取
  
  return (
    <div>
      {isLoggedIn && <AdminPanel />}
      {!isLoggedIn && <LoginForm />}
    </div>
  );
}

// 等价于if-else写法
function App() {
  const isLoggedIn = true;
  let content;
  
  if(isLoggedIn) {
    content = <AdminPanel />;
  } else {
    content = <LoginForm />;
  }
  
  return <div>{content}</div>;
}

⭕ 列表渲染 - map方法

1. 基础数组渲染

jsx 复制代码
const bookList = [
  { id: 1, title: 'Python', price: 38.5 },
  { id: 2, title: 'JavaScript', price: 42.0 },
  { id: 3, title: 'React', price: 45.0 },
  { id: 4, title: 'Vue', price: 40.0 }
];

function BookList() {
  return (
    <ul>
      {bookList.map((item) => (
        <li key={item.id}>
          {item.title} - 价格: {item.price}
        </li>
      ))}
    </ul>
  );
}

2. 箭头函数写法注意事项

jsx 复制代码
// 正确写法1 - 直接返回值(隐式return)
bookList.map(item => <li>{item.title}</li>)

// 正确写法2 - 使用大括号但显式return
bookList.map(item => {
  return <li>{item.title}</li>;
})

// 错误写法 - 使用大括号但忘记return
bookList.map(item => {
  <li>{item.title}</li> // 不会渲染,因为没有return
})

3. 复杂列表渲染示例

jsx 复制代码
function BookList() {
  return (
    <div className="book-container">
      <h2>书籍列表</h2>
      <ul className="book-list">
        {bookList.map(book => (
          <li key={book.id} className="book-item">
            <h3>{book.title}</h3>
            <p>价格: ¥{book.price.toFixed(2)}</p>
            <button onClick={() => addToCart(book)}>加入购物车</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

⭕ 关键注意事项

  1. key属性:列表中的每个元素都应该有一个唯一的key prop,通常是数据的ID

    jsx 复制代码
    // 正确
    {items.map(item => <li key={item.id}>{item.name}</li>)}
    
    // 避免使用索引作为key(除非列表不会变化)
    {items.map((item, index) => <li key={index}>{item.name}</li>)}
  2. 复杂条件渲染:对于更复杂的条件,可以使用三元表达式或提取为函数

    jsx 复制代码
    // 三元表达式
    {isLoading ? <Spinner /> : <Content />}
    
    // 提取为函数
    function renderContent() {
      if(isLoading) return <Spinner />;
      if(isError) return <Error />;
      return <Content />;
    }
  3. 性能优化:避免在渲染函数中直接定义函数或创建新数组

    jsx 复制代码
    // 不推荐(每次渲染都创建新函数)
    {items.map(item => <Item onClick={() => handleClick(item)} />)}
    
    // 推荐(提前定义处理函数)
    function renderItem(item) {
      return <Item onClick={handleClick} data={item} />;
    }

🔴 虚拟dom

⭕ 虚拟DOM原理

1. 原生JavaScript渲染流程

javascript 复制代码
// 原生JS直接操作真实DOM
const bookList = getDataFromAPI(); // 从API获取数据
const ul = document.createElement('ul');

bookList.forEach(book => {
  const li = document.createElement('li');
  li.textContent = `${book.title} - ¥${book.price}`;
  ul.appendChild(li);
});

document.body.appendChild(ul);

// 数据更新时重新创建所有DOM
function updateBooks(newBooks) {
  ul.innerHTML = ''; // 清空现有DOM
  newBooks.forEach(book => {
    const li = document.createElement('li');
    li.textContent = `${book.title} - ¥${book.price}`;
    ul.appendChild(li);
  });
}

2. React虚拟DOM流程

jsx 复制代码
// React使用虚拟DOM
function BookList({ books }) {
  return (
    <ul>
      {books.map(book => (
        <li key={book.id}>{book.title} - ¥{book.price}</li>
      ))}
    </ul>
  );
}

// 数据更新时React的对比过程
const oldVDOM = (
  <ul>
    <li key="10">Python - ¥38.5</li>
    <li key="20">JavaScript - ¥42.0</li>
  </ul>
);

const newVDOM = (
  <ul>
    <li key="15">C++ - ¥100</li>
    <li key="10">Python - ¥38.5</li>
    <li key="20">JavaScript - ¥42.0</li>
  </ul>
);

// React会识别出只有key="15"是新元素

⭕ key属性的正确使用

1. 错误用法 - 使用数组索引

jsx 复制代码
// 不推荐:使用数组索引作为key
{bookList.map((book, index) => (
  <li key={index}>{book.title}</li>
))}

2. 正确用法 - 使用唯一ID

jsx 复制代码
// 推荐:使用数据本身的唯一ID
{bookList.map(book => (
  <li key={book.id}>{book.title}</li>
))}

3. 没有ID时的替代方案

jsx 复制代码
// 如果没有ID,可以生成稳定hash
{bookList.map(book => {
  const hash = `${book.title}-${book.price}`;
  return <li key={hash}>{book.title}</li>;
})}

⭕ Diff算法详解

1. 对比过程示例

javascript 复制代码
// 旧虚拟DOM
const oldTree = {
  type: 'ul',
  children: [
    { type: 'li', key: '10', content: 'Python' },
    { type: 'li', key: '20', content: 'JavaScript' }
  ]
};

// 新虚拟DOM
const newTree = {
  type: 'ul',
  children: [
    { type: 'li', key: '15', content: 'C++' },
    { type: 'li', key: '10', content: 'Python' },
    { type: 'li', key: '20', content: 'JavaScript' }
  ]
};

// Diff算法会:
// 1. 比较ul类型相同
// 2. 比较子元素:
//    - 发现key="15"是新元素 → 创建
//    - 发现key="10"存在 → 复用
//    - 发现key="20"存在 → 复用

2. 使用索引作为key的问题

javascript 复制代码
// 如果使用索引作为key,添加元素时:
const oldTreeWithIndex = {
  children: [
    { key: 0, content: 'Python' },  // 现在key=0
    { key: 1, content: 'JavaScript' } // key=1
  ]
};

const newTreeWithIndex = {
  children: [
    { key: 0, content: 'C++' },     // 现在key=0
    { key: 1, content: 'Python' },  // key=1 (原来是Python)
    { key: 2, content: 'JavaScript' } // key=2
  ]
};

// Diff结果:
// key=0: 内容从Python变为C++ → 更新
// key=1: 内容从JavaScript变为Python → 更新
// key=2: 新增 → 创建
// 实际上只需要添加C++,但导致所有元素都被处理

⭕ 性能优化建议

  1. 始终使用稳定key:避免使用索引,使用数据唯一标识
  2. 避免频繁重新渲染:使用React.memo、useMemo等优化
  3. 最小化DOM操作:虚拟DOM的优势在于批量更新
  4. 复杂列表考虑虚拟滚动:对于超长列表使用react-window等库
jsx 复制代码
// 优化示例
const MemoBookItem = React.memo(({ book }) => {
  return <li>{book.title} - ¥{book.price}</li>;
});

function OptimizedBookList({ books }) {
  return (
    <ul>
      {books.map(book => (
        <MemoBookItem key={book.id} book={book} />
      ))}
    </ul>
  );
}

🔴 组件

⭕ 组件基础概念

组件是React应用的核心构建块,它将UI拆分为独立可复用的代码片段。组件主要包含三要素:

  • HTML(JSX) - 负责UI结构
  • CSS - 负责样式表现
  • JavaScript - 负责逻辑交互

⭕ 函数组件开发

1. 基本结构

jsx 复制代码
// 函数组件基本形式
function HelloFC() {
  // 数据定义
  const message = "中国联通";
  
  // 返回JSX
  return (
    <div>
      <p>{message}</p>
    </div>
  );
}

// 使用组件
ReactDOM.render(<HelloFC />, document.getElementById('root'));

2. 函数组件特点

  • 使用function关键字定义
  • 必须返回JSX
  • 数据定义在函数体内
  • 简单直接,适合无状态UI

⭕ 类组件开发

1. 基本结构

jsx 复制代码
// 类组件基本形式
class HelloCC extends React.Component {
  // 数据定义
  bookList = [
    { id: 1, title: 'Python', price: 38.5 },
    { id: 2, title: 'JavaScript', price: 42.0 }
  ];
  
  // 必须有的render方法
  render() {
    return (
      <ul>
        {this.bookList.map(book => (
          <li key={book.id}>
            {book.title} - ¥{book.price}
          </li>
        ))}
      </ul>
    );
  }
}

// 使用组件
ReactDOM.render(<HelloCC />, document.getElementById('root'));

2. 类组件特点

  • 使用class关键字定义
  • 必须继承React.Component
  • 必须有render()方法
  • 数据定义在类中,通过this访问
  • 功能更强大,适合复杂组件

⭕ this关键字详解

1. this在类中的使用

javascript 复制代码
class Person {
  data = {
    name: '院',
    age: 22
  };
  
  showInfo() {
    // 正确访问实例属性
    console.log(this.data);
    
    // 错误写法(直接访问data会报错)
    // console.log(data); // ReferenceError: data is not defined
  }
}

const p1 = new Person();
p1.showInfo(); // 正确输出 {name: "院", age: 22}

2. this指向规则

  • 在类方法中,this指向当前类的实例对象
  • 谁调用方法,方法中的this就指向谁
  • 在React类组件中,render()方法中的this指向组件实例

⭕ 组件对比与选择

特性 函数组件 类组件
语法 函数声明 class继承
状态 需使用Hooks 内置state
生命周期 使用Hooks模拟 内置方法
this 无this 需要使用this
代码量 简洁 相对复杂
适用场景 简单UI、无状态 复杂逻辑、有状态

⭕ 最佳实践建议

  1. 新项目优先使用函数组件:配合Hooks可以实现绝大多数功能
  2. 理解this机制:类组件开发必须掌握this指向
  3. 组件拆分原则:单一职责,一个组件只做一件事
  4. 命名规范:组件名使用PascalCase(大驼峰)命名法
  5. props使用:无论是函数还是类组件,都通过props接收外部数据
jsx 复制代码
// 函数组件接收props
function Greeting(props) {
  return <h1>Hello, {props.name}</h1>;
}

// 类组件接收props
class Greeting extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

🔴 组件嵌套

⭕ 组件嵌套基础概念

组件嵌套是React开发中的核心模式,它允许我们将UI拆分为独立可复用的部分,并通过组合方式构建复杂界面。

基本嵌套示例

jsx 复制代码
// 函数组件
function HelloFC() {
  return (
    <div>
      <p>中国联通</p>
      <HelloCC />  {/* 嵌套类组件 */}
    </div>
  );
}

// 类组件
class HelloCC extends React.Component {
  render() {
    return (
      <ul>
        {this.props.books.map(book => (
          <li key={book.id}>{book.title}</li>
        ))}
      </ul>
    );
  }
}

// 使用嵌套组件
ReactDOM.render(<HelloFC />, document.getElementById('root'));

⭕ 容器组件模式

1. 容器组件结构

jsx 复制代码
// 容器组件(通常为类组件)
class App extends React.Component {
  render() {
    return (
      <div className="app-container">
        <Header />
        <MainContent />
        <Footer />
      </div>
    );
  }
}

// 使用容器组件
ReactDOM.render(<App />, document.getElementById('root'));

2. 实际项目中的典型结构

jsx 复制代码
// App.js (根组件)
function App() {
  return (
    <div className="app">
      <Navbar />
      <div className="content">
        <Sidebar />
        <Main />
      </div>
      <Footer />
    </div>
  );
}

// Main.js (内容组件)
function Main() {
  return (
    <main>
      <Banner />
      <ProductList />
      <Newsletter />
    </main>
  );
}

⭕ 组件嵌套关键点

  1. 组件引用语法

    jsx 复制代码
    // 正确引用方式
    <ComponentName />
    
    // 错误方式(会报错)
    ComponentName
  2. 组件通信方式

    jsx 复制代码
    // 父传子 - 通过props
    function Parent() {
      return <Child message="Hello" />;
    }
    
    function Child(props) {
      return <p>{props.message}</p>;
    }
  3. 组件位置规则

    • 组件必须首字母大写(PascalCase)
    • 组件可以在任何其他组件中使用
    • 组件可以多层嵌套

⭕ 复杂嵌套示例

jsx 复制代码
// 用户信息卡片组件
function UserCard({ user }) {
  return (
    <div className="user-card">
      <Avatar image={user.avatar} />
      <div className="user-info">
        <UserName name={user.name} />
        <UserStats stats={user.stats} />
      </div>
    </div>
  );
}

// 用户列表组件
function UserList({ users }) {
  return (
    <div className="user-list">
      {users.map(user => (
        <UserCard key={user.id} user={user} />
      ))}
    </div>
  );
}

// 主页面组件
function HomePage() {
  const [users, setUsers] = useState([]);
  
  useEffect(() => {
    // 获取用户数据
    fetchUsers().then(data => setUsers(data));
  }, []);

  return (
    <div className="home-page">
      <Header />
      <Sidebar />
      <MainContent>
        <UserList users={users} />
        <RecentActivities />
      </MainContent>
      <Footer />
    </div>
  );
}

⭕ 最佳实践建议

  1. 单一职责原则:每个组件只负责一个特定功能
  2. 合理拆分:当组件超过200行代码时考虑拆分
  3. props命名:使用清晰明确的props名称
  4. 避免过度嵌套:通常不超过3-4层嵌套
  5. 容器与展示分离
    • 容器组件:负责数据获取和状态管理
    • 展示组件:负责UI渲染
jsx 复制代码
// 容器组件示例
function UserContainer() {
  const [users, setUsers] = useState([]);
  
  useEffect(() => {
    fetchUsers().then(setUsers);
  }, []);

  return <UserList users={users} />;
}

// 展示组件示例
function UserList({ users }) {
  return (
    <ul>
      {users.map(user => (
        <UserItem key={user.id} user={user} />
      ))}
    </ul>
  );
}

🔴 绑定事件

⭕ 事件绑定的核心问题

1. 原生DOM与React事件绑定区别

jsx 复制代码
// 原生HTML事件绑定
<button onclick="handleClick()">Click</button>

// React事件绑定
<button onClick={handleClick}>Click</button>

关键区别:

  • React使用小驼峰命名(onClick)
  • 不需要引号包裹
  • 传递函数引用而非字符串

⭕ 类组件中事件绑定的三种解决方案

方案1:箭头函数包裹

jsx 复制代码
class App extends React.Component {
  data = [1, 2, 3];
  
  handleClick = () => {
    console.log(this.data); // 正确访问实例属性
  };

  render() {
    return (
      <button onClick={() => this.handleClick()}>
        按钮1
      </button>
    );
  }
}

方案2:bind绑定

jsx 复制代码
class App extends React.Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }
  
  handleClick() {
    console.log(this.data); // 正确访问实例属性
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        按钮2
      </button>
    );
  }
}

方案3:类属性箭头函数

jsx 复制代码
class App extends React.Component {
  handleClick = () => {
    console.log(this.data); // 正确访问实例属性
  };

  render() {
    return (
      <button onClick={this.handleClick}>
        按钮3
      </button>
    );
  }
}

⭕ this丢失原理分析

javascript 复制代码
class Example {
  value = 42;
  
  method() {
    console.log(this.value);
  }
}

const instance = new Example();
const method = instance.method; // this丢失
method(); // TypeError: Cannot read property 'value' of undefined

React事件绑定中的this丢失问题本质上是由于函数引用传递导致的上下文丢失。

⭕ 事件对象(event)的使用

jsx 复制代码
class App extends React.Component {
  handleClick = (e) => {
    console.log('事件对象:', e);
    console.log('目标元素:', e.target);
    console.log('按钮文本:', e.target.innerHTML);
    console.log('按钮值:', e.target.value);
  };

  render() {
    return (
      <button onClick={this.handleClick}>
        点击我
      </button>
    );
  }
}

⭕ 性能优化建议

  1. 避免在render中使用箭头函数

    jsx 复制代码
    // 不推荐(每次渲染创建新函数)
    <button onClick={() => this.handleClick()}>
    
    // 推荐(提前绑定)
    <button onClick={this.handleClick}>
  2. 参数传递方式

    jsx 复制代码
    // 方式1:bind传参
    <button onClick={this.handleClick.bind(this, id)}>
    
    // 方式2:箭头函数传参
    <button onClick={(e) => this.handleClick(id, e)}>
    
    // 方式3:data-attributes
    <button data-id={id} onClick={this.handleClick}>
  3. 事件委托:对于列表项事件,推荐在父元素上处理

⭕ 综合示例

jsx 复制代码
class EventDemo extends React.Component {
  state = {
    count: 0,
    items: ['Item 1', 'Item 2', 'Item 3']
  };

  // 方案3:类属性箭头函数
  handleIncrement = () => {
    this.setState(prev => ({ count: prev.count + 1 }));
  };

  // 方案2:constructor中bind
  constructor(props) {
    super(props);
    this.handleDecrement = this.handleDecrement.bind(this);
  }
  
  handleDecrement() {
    this.setState(prev => ({ count: prev.count - 1 }));
  }

  // 带参数的事件处理
  handleItemClick = (index, e) => {
    console.log(`点击了第${index + 1}项`, e.target);
  };

  render() {
    return (
      <div>
        <h1>计数器: {this.state.count}</h1>
        
        {/* 三种绑定方式示例 */}
        <button onClick={this.handleIncrement}>增加</button>
        <button onClick={this.handleDecrement}>减少</button>
        <button onClick={() => console.log('内联箭头函数')}>
          内联箭头函数
        </button>
        
        {/* 列表项事件处理 */}
        <ul>
          {this.state.items.map((item, index) => (
            <li 
              key={index}
              onClick={(e) => this.handleItemClick(index, e)}
            >
              {item}
            </li>
          ))}
        </ul>
      </div>
    );
  }
}

🔴 refs属性

⭕ Refs 的核心作用

Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素。主要用途包括:

  • 管理焦点、文本选择或媒体播放
  • 触发强制动画
  • 集成第三方 DOM 库

⭕ 创建和使用 Refs 的步骤

1. 创建 Refs 对象(推荐方式)

jsx 复制代码
class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    // 第一步:创建 Refs
    this.myRef = React.createRef();
  }

  render() {
    // 第二步:绑定 Refs
    return <div ref={this.myRef} />;
  }
}

2. 访问 Refs

jsx 复制代码
componentDidMount() {
  // 第三步:访问 DOM 节点
  const node = this.myRef.current;
  console.log(node);
}

⭕ 不同场景下的 Refs 使用

1. 访问 DOM 元素

jsx 复制代码
class TextInput extends React.Component {
  constructor(props) {
    super(props);
    this.textInput = React.createRef();
  }

  focusTextInput = () => {
    // 直接访问 DOM API
    this.textInput.current.focus();
  };

  render() {
    return (
      <div>
        <input type="text" ref={this.textInput} />
        <button onClick={this.focusTextInput}>
          聚焦输入框
        </button>
      </div>
    );
  }
}

2. 访问类组件实例

jsx 复制代码
class AutoFocusTextInput extends React.Component {
  constructor(props) {
    super(props);
    this.textInput = React.createRef();
  }

  componentDidMount() {
    // 调用子组件的方法
    this.textInput.current.focusTextInput();
  }

  render() {
    return (
      <CustomTextInput ref={this.textInput} />
    );
  }
}

3. 函数组件中使用 Refs(forwardRef)

jsx 复制代码
const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref} className="fancy-button">
    {props.children}
  </button>
));

// 父组件中使用
const ref = React.createRef();
<FancyButton ref={ref}>点击我</FancyButton>;

⭕ 回调 Refs(旧版 API)

jsx 复制代码
class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    this.textInput = null;
    this.setTextInputRef = element => {
      this.textInput = element;
    };
  }

  focusTextInput = () => {
    if (this.textInput) this.textInput.focus();
  };

  render() {
    return (
      <div>
        <input
          type="text"
          ref={this.setTextInputRef}
        />
        <button onClick={this.focusTextInput}>
          聚焦输入框
        </button>
      </div>
    );
  }
}

⭕ Refs 使用注意事项

  1. 避免过度使用 Refs:优先考虑使用 React 的数据流(props 和 state)
  2. 不要在函数组件上使用 ref 属性:除非使用 forwardRef
  3. componentDidMount 或 componentDidUpdate 中访问 Refs:确保 DOM 已经渲染完成
  4. 避免在 render 方法中访问 refs:此时 ref 可能还未被设置

⭕ 综合示例

jsx 复制代码
class RefDemo extends React.Component {
  constructor(props) {
    super(props);
    // 创建多个 Refs
    this.inputRef = React.createRef();
    this.divRef = React.createRef();
    this.childRef = React.createRef();
  }

  componentDidMount() {
    // 自动聚焦输入框
    this.inputRef.current.focus();
    
    // 获取 DOM 元素信息
    console.log('Div 高度:', this.divRef.current.clientHeight);
  }

  handleClick = () => {
    // 调用子组件方法
    this.childRef.current.sayHello();
    
    // 获取输入值
    alert(`输入的值: ${this.inputRef.current.value}`);
  };

  render() {
    return (
      <div ref={this.divRef}>
        <h2>Refs 示例</h2>
        
        <input 
          type="text" 
          ref={this.inputRef} 
          placeholder="自动聚焦的输入框"
        />
        
        <ChildComponent ref={this.childRef} />
        
        <button onClick={this.handleClick}>
          执行操作
        </button>
      </div>
    );
  }
}

class ChildComponent extends React.Component {
  sayHello = () => {
    alert('Hello from Child Component!');
  };

  render() {
    return <div>子组件</div>;
  }
}

🔴 state属性

⭕ State 基础概念

State 是 React 组件内部用于存储和管理数据的对象,它是 React 数据驱动视图的核心机制。

1. State 的特点

  • 组件私有:只能在组件内部访问和修改
  • 动态变化:state 变化会触发组件重新渲染
  • 不可直接修改 :必须通过 setState() 方法更新

⭕ State 基本使用

1. 初始化 State

jsx 复制代码
class PasswordToggle extends React.Component {
  constructor(props) {
    super(props);
    // 初始化 state
    this.state = {
      inputType: 'password',
      buttonText: '显示'
    };
  }

  render() {
    return (
      <div>
        <input type={this.state.inputType} />
        <button onClick={this.togglePassword}>
          {this.state.buttonText}
        </button>
      </div>
    );
  }
}

2. 更新 State 的正确方式

jsx 复制代码
togglePassword = () => {
  // 错误方式:直接修改 state 不会触发重新渲染
  // this.state.inputType = 'text'; 
  
  // 正确方式:使用 setState
  this.setState({
    inputType: this.state.inputType === 'password' ? 'text' : 'password',
    buttonText: this.state.inputType === 'password' ? '隐藏' : '显示'
  });
};

⭕ setState 深入理解

1. setState 是异步的

jsx 复制代码
updateCount = () => {
  this.setState({ count: this.state.count + 1 });
  console.log(this.state.count); // 可能不是最新值
  
  // 获取更新后的 state
  this.setState({ count: this.state.count + 1 }, () => {
    console.log('更新后的值:', this.state.count);
  });
};

2. 基于前一个 state 更新

jsx 复制代码
incrementCount = () => {
  // 推荐方式:使用函数形式确保基于最新 state
  this.setState(prevState => ({
    count: prevState.count + 1
  }));
};

⭕ State 使用最佳实践

1. 避免深层嵌套

jsx 复制代码
// 不推荐
this.state = {
  user: {
    profile: {
      name: '',
      age: 0
    }
  }
};

// 推荐:扁平化 state
this.state = {
  userName: '',
  userAge: 0
};

2. 不可变数据

jsx 复制代码
// 数组更新
this.setState(prevState => ({
  items: [...prevState.items, newItem]
}));

// 对象更新
this.setState(prevState => ({
  user: {
    ...prevState.user,
    name: newName
  }
}));

⭕ 密码切换完整示例

jsx 复制代码
class PasswordToggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      inputType: 'password',
      buttonText: '显示密码'
    };
  }

  togglePassword = () => {
    this.setState(prevState => ({
      inputType: prevState.inputType === 'password' ? 'text' : 'password',
      buttonText: prevState.inputType === 'password' ? '隐藏密码' : '显示密码'
    }));
  };

  render() {
    return (
      <div className="password-container">
        <input 
          type={this.state.inputType} 
          placeholder="输入密码"
        />
        <button onClick={this.togglePassword}>
          {this.state.buttonText}
        </button>
      </div>
    );
  }
}

⭕ 常见问题解答

1. 为什么必须使用 setState?

  • 直接修改 state 不会触发组件重新渲染
  • setState 会合并 state 更新并批量处理
  • setState 会触发 React 的协调过程(reconciliation)

2. 为什么 state 要命名为 state?

  • React.Component 基类内置了 setState 方法
  • setState 方法会专门处理 this.state 对象
  • 约定优于配置,保持代码一致性

3. 如何组织复杂 state?

  • 将相关 state 分组为多个 state 变量
  • 对于复杂逻辑,考虑使用 useReducer (函数组件)
  • 避免深层嵌套的数据结构

🔴 props属性基本操作

⭕ State 的核心概念

  1. State 的作用

    • State 是 React 组件的核心状态属性
    • 修改 state 值后会触发重新渲染
  2. setState 的行为

    • 每次调用 setState 会做两件事:
      1. 修改 state 值
      2. 重新调用 render 函数进行渲染
jsx 复制代码
// 示例:setState 触发重新渲染
toggle = () => {
  this.setState({ isActive: !this.state.isActive }); // 修改值并触发重新渲染
}
  1. 重要警告
    • 绝对不能在 render 方法中调用 setState,会导致无限循环
jsx 复制代码
// 错误示例:会导致无限循环
render() {
  this.setState({ number: this.state.number + 1 }); // 错误!
  return <div>{this.state.number}</div>;
}

⭕ Props 详解

父子组件概念

  • 组件嵌套时,外层是父组件,内层是子组件
  • 同级组件称为兄弟组件

Props 基本使用

  1. 父组件传递数据
    • 通过属性方式传递数据给子组件
jsx 复制代码
// 父组件传递数据
<ShowPerson 
  name="Eric" 
  age={32} 
  gender="男" 
/>
  1. 子组件接收数据
    • 通过 this.props 接收父组件传递的数据
jsx 复制代码
// 子组件接收数据
class ShowPerson extends React.Component {
  render() {
    const { name, age, gender } = this.props;
    return (
      <ul>
        <li>姓名:{name}</li>
        <li>年龄:{age}</li>
        <li>性别:{gender}</li>
      </ul>
    );
  }
}

Props 验证

  1. 为什么需要验证

    • 确保传递的数据类型正确
    • 确保必填字段有值
    • 限制枚举值的范围
  2. 使用 prop-types 库

jsx 复制代码
// 引入 prop-types
import PropTypes from 'prop-types';

class ShowPerson extends React.Component {
  // 类静态属性方式定义 propTypes
  static propTypes = {
    name: PropTypes.string.isRequired,
    age: PropTypes.number.isRequired,
    gender: PropTypes.oneOf(['男', '女']).isRequired
  };
  
  render() {
    // ...
  }
}
  1. 常见验证类型
    • PropTypes.string - 字符串
    • PropTypes.number - 数字
    • PropTypes.bool - 布尔值
    • PropTypes.array - 数组
    • PropTypes.object - 对象
    • PropTypes.func - 函数
    • PropTypes.oneOf(['值1', '值2']) - 枚举值

⭕ 类组件的静态属性和方法

  1. 静态成员
    • 使用 static 关键字定义
    • 属于类本身,而不是实例
jsx 复制代码
class Person {
  // 静态属性
  static message = "Hello React";
  
  // 静态方法
  static bar() {
    console.log("静态方法");
  }
  
  // 实例方法
  sayHello() {
    console.log("实例方法");
  }
}

// 使用静态成员
Person.message; // 访问静态属性
Person.bar();   // 调用静态方法

// 使用实例成员
const p = new Person();
p.sayHello();   // 调用实例方法
  1. 为什么 propTypes 要定义为静态属性
    • propTypes 属于组件类的元信息
    • 不需要每个实例都保存一份
    • 可以保持代码组织更清晰

⭕ 数据传递的注意事项

  1. 数据类型问题
    • 注意数字和字符串的区别
    • 错误的类型可能导致意外行为
jsx 复制代码
// 正确:传递数字
<ShowPerson age={32} />

// 错误:传递字符串数字
<ShowPerson age="32" /> // 会导致 "32" + 1 = "321"
  1. 解构传递
    • 可以将对象解构为多个 props
jsx 复制代码
const personInfo = {
  name: "张三",
  age: 32,
  gender: "男"
};

<ShowPerson {...personInfo} />
  1. 默认值
    • 可以为 props 设置默认值
jsx 复制代码
static defaultProps = {
  age: 18,
  gender: '未知'
};

🔴 子组件传递数据给父组件

⭕ 核心概念

子组件向父组件传递数据的基本原理是:父组件通过props向子组件传递一个函数,子组件调用这个函数来间接修改父组件的状态

⭕ 实现步骤

1. 父组件准备

jsx 复制代码
class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      number: 0
    };
  }

  // 父组件定义修改自身状态的方法
  changeNumber = () => {
    this.setState(prevState => ({
      number: prevState.number + 1
    }));
  }

  render() {
    return (
      <div>
        <p>父组件的当前number值: {this.state.number}</p>
        {/* 将方法和状态传递给子组件 */}
        <Child 
          changeParentNumber={this.changeNumber} 
          currentNumber={this.state.number}
        />
      </div>
    );
  }
}

2. 子组件实现

jsx 复制代码
class Child extends React.Component {
  handleClick = () => {
    // 调用父组件传递过来的方法
    this.props.changeParentNumber();
  }

  render() {
    return (
      <div>
        <button onClick={this.handleClick}>
          子组件按钮 - 点击增加父组件的number
        </button>
        <p>从父组件接收的number值: {this.props.currentNumber}</p>
      </div>
    );
  }
}

⭕ 两种实现方式对比

方式一:直接传递方法

jsx 复制代码
// 父组件
changeNumber = () => {
  this.setState({ number: this.state.number + 1 });
}

// 传递给子组件
<Child changeParentNumber={this.changeNumber} />

// 子组件调用
this.props.changeParentNumber();

方式二:传递带参数的方法

jsx 复制代码
// 父组件
changeNumber = (newValue) => {
  this.setState({ number: newValue });
}

// 传递给子组件
<Child 
  changeParentNumber={this.changeNumber} 
  currentNumber={this.state.number}
/>

// 子组件调用
this.props.changeParentNumber(this.props.currentNumber + 1);

⭕ 关键点说明

  1. this绑定问题

    • 推荐使用箭头函数定义方法,避免this指向问题
    • 或者在构造函数中绑定this:this.changeNumber = this.changeNumber.bind(this)
  2. 数据流向

    • 数据总是从父组件流向子组件(单向数据流)
    • 子组件不能直接修改父组件的props
    • 必须通过父组件提供的方法间接修改
  3. 最佳实践

    • 父组件方法命名清晰,如handleXXXonXXX
    • 子组件props命名有意义,如onClickonChange
    • 考虑使用函数组件和hooks简化代码

⭕ 完整示例代码

jsx 复制代码
import React from 'react';

// 父组件
class Parent extends React.Component {
  state = {
    number: 0
  };

  // 使用箭头函数避免this绑定问题
  incrementNumber = () => {
    this.setState(prevState => ({
      number: prevState.number + 1
    }));
  };

  render() {
    return (
      <div>
        <h2>父组件</h2>
        <p>当前值: {this.state.number}</p>
        <Child 
          onIncrement={this.incrementNumber} 
          currentValue={this.state.number}
        />
      </div>
    );
  }
}

// 子组件
class Child extends React.Component {
  handleClick = () => {
    this.props.onIncrement();
  };

  render() {
    return (
      <div>
        <h3>子组件</h3>
        <button onClick={this.handleClick}>
          增加父组件的值
        </button>
        <p>从父组件接收的值: {this.props.currentValue}</p>
      </div>
    );
  }
}

export default Parent;

⭕ 函数组件版本

jsx 复制代码
import React, { useState } from 'react';

// 父组件
function Parent() {
  const [number, setNumber] = useState(0);

  const incrementNumber = () => {
    setNumber(prev => prev + 1);
  };

  return (
    <div>
      <h2>父组件</h2>
      <p>当前值: {number}</p>
      <Child 
        onIncrement={incrementNumber} 
        currentValue={number}
      />
    </div>
  );
}

// 子组件
function Child({ onIncrement, currentValue }) {
  return (
    <div>
      <h3>子组件</h3>
      <button onClick={onIncrement}>
        增加父组件的值
      </button>
      <p>从父组件接收的值: {currentValue}</p>
    </div>
  );
}

export default Parent;
相关推荐
小桥风满袖几秒前
Three.js-硬要自学系列33之专项学习基础材质
前端·css·three.js
聪明的水跃鱼5 分钟前
Nextjs15 构建API端点
前端·next.js
小明爱吃瓜22 分钟前
AI IDE(Copilot/Cursor/Trae)图生代码能力测评
前端·ai编程·trae
不爱说话郭德纲27 分钟前
🔥Vue组件的data是一个对象还是函数?为什么?
前端·vue.js·面试
绅士玖29 分钟前
JavaScript 中的 arguments、柯里化和展开运算符详解
前端·javascript·ecmascript 6
GIS之路32 分钟前
OpenLayers 图层控制
前端
断竿散人32 分钟前
专题一、HTML5基础教程-http-equiv属性深度解析:网页的隐形控制中心
前端·html
星河丶32 分钟前
介绍下navigator.sendBeacon方法
前端
curdcv_po33 分钟前
🤸🏼🤸🏼🤸🏼兄弟们开源了,用ThreeJS还原小米SU7超跑!
前端
我是小七呦33 分钟前
😄我再也不用付费使用PDF工具了,我在Web上实现了一个PDF预览/编辑工具
前端·javascript·面试