电子版: React 模式
一份关于 react 的学习资料
函数组件
函数组件 是最简单的一种声明可复用组件的方法
特色 | 代码 |
---|
基本Greeting | function Greeting() {
return <div>Hi there!</div>;
}
|
从第一个形参中获取属性集 (props) | function Greeting(props) {
return <div>Hi {props.name}!</div>;
}
|
按自己的需要可以在函数组件中定义任意变量 | function Greeting(props) {
let style = {
fontWeight: "bold",
color: context.color
};
return <div style={style}>Hi {props.name}!</div>;
}
|
使用 defaultProps 为任意必有属性设置默认值 | function Greeting(props) {
return <div>Hi {props.name}!</div>;
}
Greeting.defaultProps = {
name: "Guest"
};
|
属性解构 (Destructuring props)
特性 | 代码 |
---|
基本语法 | // 好比字面量赋值的反转形式。
let person = { name: "chantastic" };
let { name } = person;
// 同样适用于数组。
let things = ["one", "two"];
let [first, second] = things;
|
解构赋值被用在很多 函数组件 | function Greeting(props) {
return <div>Hi {props.name}!</div>;
}
function Greeting({ name }) {
return <div>Hi {name}!</div>;
}
|
剩余参数(Rest) | function Greeting({ name, ...restProps }) {
return <div>Hi {name}!</div>;
}
|
JSX 中的属性展开 (JSX spread attributes)
它是一种语法,专门用来把对象上的属性转换成 JSX 中的属性
特性 | 代码 |
---|
我们可以 扩散 restProps 对象的所有属性到 div 元素上 | function Greeting({ name, ...restProps }) {
return <div {...restProps}>Hi {name}!</div>;
}
|
这让 Gretting 组件变得非常灵活,我们可以通过传给 Gretting 组件 DOM 属性并确定这些属性一定会被传到 div 上 | <Greeting name="Fancy pants" className="fancy-greeting" id="user-greeting" />
|
合并解构属性和其它值
组件就是一种抽象,好的抽象是可以扩展的。
特性 | 代码 |
---|
比如说下面这个组件使用 class 属性来给按钮添加样式 | function MyButton(props) {
return <button className="btn" {...props} />;
}
<MyButton className="delete-btn">Delete...</MyButton>
|
JSX 中的属性展开 对先后顺序是敏感的 | // 扩散属性中的 className 会覆盖组件上的 className。
// 我们可以改变它两的顺序,但是目前来说 className 只有 btn。
function MyButton(props) {
return <button {...props} className="btn" />;
}
|
扩展上面的情况 | // 实际中,我们可以用 classnames 这个库来完成下面的逻辑
function MyButton({ className = "", ...props }) {
let classNames = ["btn", className].join(" ");
return <button className={classNames} {...props} />;
}
|
条件渲染 (Conditional rendering)
- 不可以在一个组件声明中使用 if/else 语句 You can't use if/else statements inside a component declarations.
- 所以可以使用 条件(三元)运算符 和 短路计算。
特性 | 代码 |
---|
如果(if) | {
condition && <span>Rendered when `truthy`</span>;
}
|
除非(else),其实就是 unless | {
condition || <span>Rendered when `falsy`</span>;
}
|
如果-否则 | {
condition ? (
<span>Rendered when `truthy`</span>
) : (
<span>Rendered when `falsy`</span>
);
}
|
子元素类型 (Children types)
特性 | 代码 |
---|
字符串 String | <div>Hello World!</div>
|
数组 Array | <div>{["Hello ", <span>World</span>, "!"]}</div>
|
我们使用 map() 方法创建一个新的 React 元素数组 | // 方式1
<ul>
{["first", "second"].map(item => (
<li>{item}</li>
))}
</ul>
// 等价形式
<ul>{[<li>first</li>, <li>second</li>]}</ul>
|
联合解构、JSX 属性扩散以及其它组件一起使用,简洁无比 | <ul>
{arrayOfMessageObjects.map(({ id, ...message }) => (
<Message key={id} {...message} />
))}
</ul>
|
函数做为子元素 (Function as children) | React 组件不支持函数类型的子元素。
然而 渲染属性 是一种可以创建组件并以函数作为子元素的模式。
|
渲染属性 (Render prop) | // 字义组件
const Width = ({ children }) => children(500);
// 使用组件
<Width>{width => <div>window is {width}</div>}</Width>
// 渲染策略
<Width>
{width => (width > 600 ? <div>min-width requirement met!</div> : null)}
</Width>
|
代理组件 (Proxy component)
(我并不确定这个名字的准确叫法 译:代理、中介、装饰?)
特性 | 代码 |
---|
将一些属性进行封装 | 按钮在 web 应用中随处可见。并且所有的按钮都需要一个 type="button" 的属性。
<button type="button">
重复的写这些属性很容易出错。我们可以写一个高层组件来代理 props 到底层组件。
const Button = props =>
<button type="button" {...props}>
|
样式组件 (Style component) | import classnames from "classnames";
const PrimaryBtn = props => <Btn {...props} primary />;
const Btn = ({ className, primary, ...props }) => (
<button
type="button"
className={classnames("btn", primary && "btn-primary", className)}
{...props}
/>
);
|
样式组件-几种形式 | <PrimaryBtn />
<Btn primary />
<button type="button" className="btn btn-primary" />
|
布局组件 (Layout component)
布局组件表现为一些静态 DOM 元素的形式。它们一般并不需要经常更新。
特性 | 代码 |
---|
就像下面的这个组件一样,两边各自渲染了一个 children。 | <HorizontalSplit
leftSide={<SomeSmartComponent />}
rightSide={<AnotherSmartComponent />}
/>
|
HorizontalSplit 组件是两个子组件的父元素,我们可以告诉组件永远都不要更新 | class HorizontalSplit extends React.Component {
shouldComponentUpdate() {
return false;
}
render() {
<FlexContainer>
<div>{this.props.leftSide}</div>
<div>{this.props.rightSide}</div>
</FlexContainer>
}
}
|
容器组件 (Container component)
「容器用来获取数据然后渲染到子组件上,仅仅如此。」
特性 | 代码 |
---|
静态数据 | const CommentList = ({ comments }) => (
<ul>
{comments.map(comment => (
<li>
{comment.body}-{comment.author}
</li>
))}
</ul>
);
|
自带请求的数据组件 | class CommentListContainer extends React.Component {
constructor() {
super()
this.state = { comments: [] }
}
componentDidMount() {
$.ajax({
url: "/my-comments.json",
dataType: 'json',
success: comments =>
this.setState({comments: comments});
})
}
render() {
return <CommentList comments={this.state.comments} />
}
}
|
高阶组件 (Higher-order component)
特性 | 代码 |
---|
其实就一个函数,能返回一个组件的函数 | const Connect = ComposedComponent =>
class extends React.Component {
constructor() {
super();
this.state = { name: "" };
}
componentDidMount() {
// this would fetch or connect to a store
this.setState({ name: "Michael" });
}
render() {
return <ComposedComponent {...this.props} name={this.state.name} />;
}
};
|
状态提升 (State hoisting)
- 函数组件 没有状态 (就像名字暗示的一样)。
- 事件是状态的变化。
- 它们的数据需要传递给状态化的父 容器组件
- 这就是所谓的「状态提升」
这个状态 被提升 到了容器中,通过添加回调函数,回调中可以更新本地状态。这就设置了一个很清晰边界,并且使功能组件的可重用性最大化。
这个模式并不限于函数组件。因为函数组件没有生命周期事件,你也可以在类组件中使用这种模式。
受控输入 是一种与状态提升同时使用时很重要的模式
特性 | 代码 |
---|
它是通过将回调从容器组件传递给子组件来完成的 | class NameContainer extends React.Component {
constructor() {
super();
this.state = { name: "" };
}
render() {
return <Name onChange={newName => this.setState({ name: newName })} />;
}
}
const Name = ({ onChange }) => (
<input onChange={e => onChange(e.target.value)} />
);
|
受控输入 (Controlled input)
- 受控组件Controlled Components: 在受控组件中,组件的状态(例如input的值)由React的state管理。每当用户与表单元素交互时,onChange等事件处理函数会更新组件的状态,从而使UI与状态保持同步。这样的方式让React完全掌控组件的状态,并且使得状态的变化可以追踪和操作。
- 非受控组件Uncontrolled Components: 非受控组件中,表单元素的值不受React的state控制,而是由DOM自身维护。通常通过使用ref来获取表单元素的值,这种方式比较适合一些简单的交互,但在复杂情况下可能会导致代码难以维护和调试。
特性 | 代码 |
---|
一个不受控的(通常)输入 | <input type="text" />
<input type="text" defaultValue="1231" />
|
受控的情况 | class ControlledNameInput extends React.Component {
constructor() {
super();
this.state = { name: "" };
}
render() {
return (
<input
value={this.state.name}
onChange={(e) => this.setState({ name: e.target.value })}
/>
);
}
}
|
受控组件(Controlled Components) | import React, { Component } from 'react';
class ControlledComponent extends Component {
constructor(props) {
super(props);
this.state = {
inputValue: ''
};
}
handleInputChange = (event) => {
this.setState({ inputValue: event.target.value });
};
render() {
return (
<div>
<input
type="text"
value={this.state.inputValue}
onChange={this.handleInputChange}
/>
<p>Input Value: {this.state.inputValue}</p>
</div>
);
}
}
export default ControlledComponent;
|
非受控组件(Uncontrolled Components) | import React, { Component } from 'react';
class UncontrolledComponent extends Component {
constructor(props) {
super(props);
this.inputRef = React.createRef();
}
handleButtonClick = () => {
console.log('Input Value:', this.inputRef.current.value);
};
render() {
return (
<div>
<input type="text" ref={this.inputRef} />
<button onClick={this.handleButtonClick}>Get Input Value</button>
</div>
);
}
}
export default UncontrolledComponent;
|
非受控组件的 value 初始化 | import React, { Component } from 'react';
class UncontrolledComponentWithInitialValue extends Component {
constructor(props) {
super(props);
this.inputRef = React.createRef();
}
handleButtonClick = () => {
console.log('Input Value:', this.inputRef.current.value);
};
render() {
return (
<div>
<input type="text" defaultValue="Initial Value" ref={this.inputRef} />
<button onClick={this.handleButtonClick}>Get Input Value</button>
</div>
);
}
}
export default UncontrolledComponentWithInitialValue;
|
关于受控与非受控组件
总结来说,受控组件提供了更强大的状态管理和交互控制,适用于需要更精细控制的情况。
而非受控组件则可以在一些简单场景下减少代码量,但可能会失去React状态管理的优势。选择使用哪种方式取决于项目的需求和开发者的偏好。
参考