🔴 你老说拿下 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;
相关推荐
崔庆才丨静觅3 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60613 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了3 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅4 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅4 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅4 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment4 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅5 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊5 小时前
jwt介绍
前端
爱敲代码的小鱼5 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax