Appearance
React
React 是一个用于构建用户界面的 JAVASCRIPT 库
特点
- 1.声明式设计 −React 采用声明范式,可以轻松描述应用。
- 2.高效 −React 通过对 DOM 的模拟,最大限度地减少与 DOM 的交互。
- 3.灵活 −React 可以与已知的库或框架很好地配合。
- 4.JSX − JSX 是 JavaScript 语法的扩展。React 开发不一定使用 JSX ,但我们建议使用它。
- 5.组件 − 通过 React 构建组件,使得代码更加容易得到复用,能够很好的应用在大项目的开发中。
- 6.单向响应的数据流 − React 实现了单向响应的数据流,从而减少了重复代码,这也是它为什么比传统数据绑定更简单。
安装
react.min.js- React 的核心库react-dom.min.js- 提供与 DOM 相关的功能
html
<!-- 开发环境 -->
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<!-- 生产环境 -->
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>使用 JSX,则 <script> 标签的 type 属性需要设置为 text/babel
设置 crossorigin 属性:有更好的错误处理体验。参考
- 设置了
crossorigin就相当于开启了cors校验。 - 开启 cors 校验之后,跨域的 script 资源在运行出错的时候,
window.onerror可以捕获到完整的错误信息。 crossorigin=use-credentials可以跨域带上 cookie
Create React App
创建新的单页应用的最佳方式
shell
# 安装 create-react-app
cnpm install -g create-react-app
# 创建react
create-react-app my-app
npx create-react-app
cd my-app
npm startNext.js
一个流行的、轻量级的框架,用于配合 React 打造静态化和服务端渲染应用。它包括开箱即用的样式和路由方案
Gatsby
创建静态网站的最佳方式
React JSX
jsx:javascript + xml
jsx
const element = <h1 className="greeting">Hello, world!</h1>Babel 会把 JSX 转译成一个名为 React.createElement() 函数调用
jsx
const element = React.createElement('h1', { className: 'greeting' }, 'Hello, world!')特点
- JSX 执行更快,在编译为 JavaScript 代码后进行了优化
- 类型安全,编译过程中发现错误
- 使用 JSX 编写模板更加简单快速
JSX 引入
babel.min.js - Babel 可以将 ES6 代码转为 ES5 代码。Babel 内嵌了对 JSX 的支持。
cdn
html
<!-- 生产环境中不建议使用 -->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script type="text/babel"></script>JSX 添加到项目
shell
npm init -y
npm install babel-cli@6 babel-preset-react-app@3
mkdir src
npx babel --watch src --out-dir . --presets react-app/prod嵌入表达式
- 遇到
<>按HTML解析 {}按JavaScript解析- 存在标签结构,且标签结构需要换行,用
()包裹
jsx
ReactDOM.render(
<div>
<h1>菜鸟教程</h1>
{/*注释...*/}
</div>,
document.getElementById('example')
)JSX 里的
class变成了className,而tabindex则变为tabIndex。
JSX 防止注入攻击:React DOM 在渲染所有输入内容之前,默认会进行转义。
样式:React 推荐使用内联样式。使用 camelCase 语法来设置内联样式,会在指定元素数字后自动添加 px
:外层{}为 变量解析,内层{}为 样式对象
jsx
<ListItem style={{ color: red, fontSize: 12 }} />数组:自动展开
jsx
var arr = [<h1>菜鸟教程</h1>, <h2>学的不仅是技术,更是梦想!</h2>]
ReactDOM.render(<div>{arr}</div>, document.getElementById('example'))嵌入:允许在大括号中嵌入任何表达式
jsx
function NumberList(props) {
const numbers = props.numbers
return (
<ul>
{numbers.map((number) => (
<ListItem key={number.toString()} value={number} />
))}
</ul>
)
}元素渲染 - render
React 构建的应用通常只有单一的根 DOM 节点
React 元素是不可变对象。一旦被创建,你就无法更改它的子元素或者属性。
据已学知识,更新 UI 唯一的方式是创建一个全新的元素,并将其传入 ReactDOM.render()
ReactDOM.render() :首先会比较元素内容先后的不同,而在渲染过程中只会更新改变了的部分
js
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
)
ReactDOM.render(element, document.getElementById('root'))
}
setInterval(tick, 1000)ES6,
props变为this.props
components
组件定义
组件名称必须以大写字母开头。小写代表原生 DOM,大写代表 组件。
jsx
// 函数组件
function Welcome(props) {
return <h1>Hello, {props.name}</h1>
}
// ES6类写法
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>
}
}自定义组件
原生 HTML 元素名以小写字母开头,而自定义的 React 类名以大写字母开头
使用props 向组件传参
因为
class和for是JavaScript保留字。添加属性时,class属性需要写成className,for属性需要写成htmlFor
组件 API
- 设置状态:
setState - 替换状态:
replaceState - 设置属性:
setProps - 替换属性:
replaceProps - 强制更新:
forceUpdate - 获取 DOM 节点:
findDOMNode - 判断组件挂载状态:
isMounted
setState - 设置状态
setState(object nextState[, function callback])
nextState:设置新状态,新旧状态会进行合并callback(可选):回调函数。在*setState设置成功*,且组件重新渲染后调用。
replaceState - 替换状态
replaceState(object nextState[, function callback])
nextState:设置新状态,新状态替换旧状态将要设置的新状态callback(可选):回调函数。在*setState设置成功*,且组件重新渲染后调用
setProps - 设置属性
setProps(object nextProps[, function callback])
nextProps:设置新属性,新旧属性会进行合并callback(可选):回调函数。在*setProps设置成功*,且组件重新渲染后调用
props:从父组件向下传递至所有的子组件。
setProps():向组件传递数据或通知setProps()组件重新渲染
更新组件
- 再次调用
React.render() setProps()方法改变组件属性,触发组件重新渲染。
replaceProps - 替换属性
replaceProps(object nextProps[, function callback])
nextProps,设置的新属性,该属性会替换当前的props。callback(可选):回调函数。在*replaceProps设置成功*,且组件重新渲染后调用
forceUpdate - 强制更新
forceUpdate([function callback])
callback(可选):回调函数。会在组件render()方法调用后调用。
父组件和子组件均会调用自身 render(),组件重新渲染时,会读取this.props和this.state,没有改变,只更新 DOM。
应用:this.props和this.state之外的组件重绘(如:修改 this.state),用于通知React需要调用render()
避免使用
forceUpdate
findDOMNode - 获取 DOM 节点
DOMElement findDOMNode()
- 返回值:DOM 元素
DOMElement
组件已挂载到 DOM 中,会返回对应的本地浏览器 DOM 元素。
render返回null 或 false,findDOMNode也返回null。
应用:从DOM中取值
isMounted - 判断组件挂载状态 ^已废弃?^
bool isMounted()
- 返回值:
true或false,表示组件是否 已挂载到 DOM
应用:保证了setState()和forceUpdate()在异步调用不会出错
isMounted 的方法在 ES6 中*?已经废除*。
原因:不足以检测组件是否挂载,对于异步的程序情况,以及逻辑上造成混乱。用以下方法代替:
jsx
componentDidMount() {
this.mounted = true;
}
componentWillUnmount() {
this.mounted = false;
}组件之间传值
子传父 :通过 父元素传给 一个能改变父元素state的回调。
父传子:通过 props
兄弟之间:共同子元素,或共同父元素
State
React 把组件看成一个状态机(State Machines)。通过与用户的交互,实现不同状态,然后渲染 UI,让用户界面和数据保持一致。只需更新组件的 state,然后根据新的 state 重新渲染用户界面(不要操作 DOM)
不要直接更新状态:直接更新不会重新渲染组件。
解决:setState
jsx
// Wrong
this.state.comment = 'Hello'
// Correct
this.setState({ comment: 'Hello' })状态更新可能为异步: this.props 和 this.state 可能是异步更新。
解决:接受一个函数而不是对象
jsx
// Wrong
this.setState({
counter: this.state.counter + this.props.increment
})
// Correct
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}))
setState总会触发组件重绘,除非在shouldComponentUpdate()实现条件渲染逻辑
获取最新值
通过setState回调
jsx
this.setState(
{
count: this.state.count + 1
},
() => {
console.log(this.state.count)
}
)
console.log(this.state.count) // 此处无法获取最新值Promise + async
jsx
setStateSync(res){
return new Promise((resolve)=>{
this.setState(state,resolve)
})
}钩子函数
componentDidMount() :挂载
componentWillUnmount() :卸载
jsx
class Clock extends React.Component {
constructor(props) {
super(props)
this.state = { date: new Date() }
}
componentDidMount() {
this.timerID = setInterval(() => this.tick(), 1000)
}
componentWillUnmount() {
clearInterval(this.timerID)
}
tick() {
this.setState({ date: new Date() })
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>现在是 {this.state.date.toLocaleTimeString()}.</h2>
</div>
)
}
}
ReactDOM.render(<Clock />, document.getElementById('example'))代码执行
- 调用
Clock组件的构造函数。 初始化this.state - 调用
Clock组件的render()方法。更新 DOM 以匹配Clock的渲染输出。 Clock的输出插入到 DOM 中时,React 调用componentDidMount()生命周期钩子,触发定时器。- 浏览器每秒钟调用
tick()方法。 调用setState(),React 知道状态已经改变,调度 UI 更新,并再次调用render()方法。 Clock组件被从 DOM 中移除,React 会调用componentWillUnmount()这个钩子函数,定时器清除。
父组件或子组件都不能知道某个组件是有状态还是无状态,并且不应该关心某组件是被定义为一个函数还是一个类。 除了拥有并设置它的组件外,其它组件不可访问
自顶向下或单向数据流: 任何状态始终由某些特定组件所有,并且从该状态导出的任何数据或 UI 只能影响树中下方的组件。
Props
纯函数:不会尝试更改入参,多次调用下相同的入参始终返回相同的结果
所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。
用户自定义组件时,它会将 JSX 所接收的属性(attributes)以及子组件(children)转换为*单个对象(props)*传递给组件
state和props主要区别:props是不可变.
jsx
function Welcome(props) {
return <h1>Hello, {props.name}</h1>
}
const element = <Welcome name="Sara" />
ReactDOM.render(element, document.getElementById('root'))默认 Props
组件类的 defaultProps 属性为 props 设置默认值
jsx
class HelloMessage extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>
}
}
HelloMessage.defaultProps = {
name: 'Runoob'
}通常在父组件中设置 state, 并通过在子组件上使用 props 将其传递到子组件上。
jsx
class WebSite extends React.Component {
constructor() {
super()
this.state = {
name: '菜鸟教程',
site: 'https://www.runoob.com'
}
}
render() {
return (
<div>
<Name name={this.state.name} />
<Link site={this.state.site} />
</div>
)
}
}
class Name extends React.Component {
render() {
return <h1>{this.props.name}</h1>
}
}
class Link extends React.Component {
render() {
return <a href={this.props.site}>{this.props.site}</a>
}
}
ReactDOM.render(<WebSite />, document.getElementById('example'))Props 验证
Props 验证使用 propTypes:保证我们的应用组件被正确使用,类型错误会警告。
React.PropTypes 在 React v15.5 版本后已经移到了 prop-types 库。
html
<script src="https://cdn.bootcss.com/prop-types/15.6.1/prop-types.js"></script>jsx
var title = '菜鸟教程'
// var title = 123;
class MyTitle extends React.Component {
render() {
return <h1>Hello, {this.props.title}</h1>
}
}
MyTitle.propTypes = {
title: PropTypes.string
}
ReactDOM.render(<MyTitle title={title} />, document.getElementById('example'))event
| React | Html | |
|---|---|---|
| 事件绑定属性命名 | 驼峰式 | 小写 |
| 阻止默认行为 | e.preventDefault()React 不能用 false阻止默认行为 | return false |
| 事件监听 | addEventListener |
事件处理
React 元素事件处理和 DOM 元素相似,语法存在以下不同
- React 事件命名采用小驼峰式(
camelCase),而不是纯小写 - 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。
- React 不能通过返回
false方式阻止默认行为。你必须显式使用preventDefault - React 无需使用
addEventListener添加监听器
jsx
<!-- 传统HTML -->
<button onclick="activateLasers()">
Activate Lasers
</button>
<!-- React -->
<button onClick={activateLasers}>
Activate Lasers
</button>jsx
<!-- 传统HTML -->
<a href="#" onclick="console.log('The link was clicked.'); return false">
Click me
</a>
<!-- React -->
function ActionLink() {
function handleClick(e) {
e.preventDefault();
console.log('The link was clicked.');
}
return (
<a href="#" onClick={handleClick}>
Click me
</a>
);
}JavaScript 中,class 的方法默认不会绑定 this。使用 bind函数绑定
jsx
class Toggle extends React.Component {
constructor(props) {
super(props)
this.state = { isToggleOn: true }
// 这边绑定是必要的,这样 `this` 才能在回调函数中使用
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
this.setState((prevState) => ({
isToggleOn: !prevState.isToggleOn
}))
}
render() {
return <button onClick={this.handleClick}>{this.state.isToggleOn ? 'ON' : 'OFF'}</button>
}
}
ReactDOM.render(<Toggle />, document.getElementById('example'))使用实验性的 public class fields语法,可以直接添加箭头函数可以省略 bind
Create React App默认启用此语法
jsx
class LoggingButton extends React.Component {
// 确保了 `this` 绑定在 handleClick 中
handleClick = () => {
console.log('this is:', this)
}
}没有使用 class fields 语法,在回调中使用箭头函数
jsx
class LoggingButton extends React.Component {
handleClick() {
console.log('this is:', this)
}
render() {
// 此语法确保 `handleClick` 内的 `this` 已被绑定。
return <button onClick={() => this.handleClick()}>Click me</button>
}
}问回调函数作为一个属性值传入低阶组件,组件可能会进行额外的重新渲染。
建议构造函数中使用
bind或public class fields语法
jsx
class LoggingButton extends React.Component {
render() {
// 这个语法确保了 `this` 绑定在 handleClick 中
return <button onClick={(e) => this.handleClick(e)}>Click me</button>
}
}事件传参
箭头函数:事件参数必须显示传递。
bind函数:事件对象是隐式传递。事件对象e排在所有参数之后
jsx
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>条件渲染
if else
jsx
function Greeting({ isLoggedIn }) {
if (isLoggedIn) {
return <UserGreeting />
}
return <GuestGreeting />
}
ReactDOM.render(
// Try changing to isLoggedIn={true}:
<Greeting isLoggedIn={false} />,
document.getElementById('example')
)与运算符 &&
true && expression 总是会返回 expression, 而 false && expression 总是会返回 false。
jsx
function Mailbox(props) {
const unreadMessages = props.unreadMessages
return (
<div>
<h1>Hello!</h1>
{unreadMessages.length > 0 && <h2>You have {unreadMessages.length} unread messages.</h2>}
</div>
)
}
const messages = ['React', 'Re: React', 'Re:Re: React']
ReactDOM.render(<Mailbox unreadMessages={messages} />, document.getElementById('root'))三目运算符
condition ? true : false
jsx
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
{isLoggedIn
? <LogoutButton onClick={this.handleLogoutClick} />
: <LoginButton onClick={this.handleLoginClick} />
}
</div>
);
}*更复杂情况,考虑提取组件*
不满足单根,使用
<React.Fragment></React.Fragment>包裹,简写为<> </>
隐藏组件
让 render 方法直接返回 null,而不进行任何渲染。但componentWillUpdate 和 componentDidUpdate 依然可以被调用
列表 & Keys
列表
通过使用 {} 在 JSX 内构建一个元素集合。
jsx
const numbers = [1, 2, 3, 4, 5]
const listItems = numbers.map((numbers) => <li>{numbers}</li>)
ReactDOM.render(<ul>{listItems}</ul>, document.getElementById('example'))keys
Keys :在 DOM 中某些元素被增加或删除,帮助 React 识别哪些元素发生了变化
选择不指定显式 key 值,将默认使用索引用作为列表项目的 key 值
不建议使用索引来用作 key 值,因为这样做会导致性能变差,还可能引起组件状态的问题
jsx
const numbers = [1, 2, 3, 4, 5]
const listItems = numbers.map((number) => <li key={number.toString()}>{number}</li>)key 只有放在就近的数组上下文中才有意义
jsx
function ListItem(props) {
// 正确!这里不需要指定 key: return <li>{props.value}</li>;}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// 正确!key 应该在数组的上下文中被指定
<ListItem key={number.toString()} value={number} />
);
return (
<ul>
{listItems}
</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);key在兄弟之间必须唯一,不需要全局唯一。生成两个不同数组时,可以使用相同的 key 值
jsx
function Blog(props) {
const sidebar = (
<ul>
{props.posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
const content = props.posts.map((post) => (
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.content}</p>
</div>
))
return (
<div>
{sidebar}
<hr />
{content}
</div>
)
}
const posts = [
{ id: 1, title: 'Hello World', content: 'Welcome to learning React!' },
{ id: 2, title: 'Installation', content: 'You can install React from npm.' }
]
ReactDOM.render(<Blog posts={posts} />, document.getElementById('root'))key不会传给组件,也就是无法通过 props.key获取
表单
在 React 里,HTML 表单元素的工作方式和其他的 DOM 元素有些不同。
因为表单元素通常会保持一些内部 state。
表单具有默认的 HTML 表单行为,即在用户提交表单后浏览到新页面。在 React 中执行相同的代码,依然有效。
受控组件
HTML:单元素(如<input>、 <textarea> 和 <select>)之类的表单元素通常自己维护 state,并根据用户输入进行更新。
React:可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用 setState()来更新
两者结合(受控组件),React 的 state 成为*“唯一数据源”。渲染表单的 React 组件还控制着用户输入过程中表单发生的操作*。
受控组件:state 管理 value,非受控组件:通过操作 dom 管理组件
jsx
class NameForm extends React.Component {
constructor(props) {
super(props)
this.state = { value: '' }
this.handleChange = this.handleChange.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
}
handleChange(event) {
this.setState({ value: event.target.value })
}
handleSubmit(event) {
alert('提交的名字: ' + this.state.value)
event.preventDefault()
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
名字:
<input type="text" value={this.state.value} onChange={this.handleChange} />{' '}
</label>
<input type="submit" value="提交" />
</form>
)
}
}表单元素上设置了 value 属性,值始终为 this.state.value,使得 React 的 state 成为唯一数据源。
由 handlechange 在每次按键时都会执行并更新 state,因此显示的值将随着用户输入而更新。
textarea
HTML:通过其子元素定义其文本
jsx
<textarea>文本</textarea>React:<textarea> 使用 value 属性代替
jsx
class EssayForm extends React.Component {
/*
...
*/
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
文章:
<textarea value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="提交" />
</form>
)
}
}select
HTML:通过 selected属性选中元素
jsx
<select>
<option value="grapefruit">葡萄柚</option>
<option value="lime">酸橙</option>
<option selected value="coconut">
椰子
</option>
<option value="mango">芒果</option>
</select>React:根 select 标签上使用 value 属性
jsx
class FlavorForm extends React.Component {
/*
...
*/
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
选择你喜欢的风味:
<select value={this.state.value} onChange={this.handleChange}>
<option value="grapefruit">葡萄柚</option>
<option value="lime">酸橙</option>
<option value="coconut">椰子</option>
<option value="mango">芒果</option>
</select>
</label>
<input type="submit" value="提交" />
</form>
)
}
}多选:
<select multiple={true} value={['B', 'C']}>
文件 input
HTML:<input type="file"> 允许用户从存储设备中选择一个或多个文件,将其上传到服务器,或通过使用 JavaScript 的 File API 进行控制。
处理多个输入
处理多个 input 元素时,我们可以给每个元素添加 name 属性,根据 event.target.name 的值选择要执行的操作
jsx
class Reservation extends React.Component {
constructor(props) {
super(props)
this.state = {
isGoing: true,
numberOfGuests: 2
}
this.handleInputChange = this.handleInputChange.bind(this)
}
handleInputChange(event) {
const target = event.target
const value = target.name === 'isGoing' ? target.checked : target.value
const name = target.name
this.setState({
[name]: value
})
}
render() {
return (
<form>
<label>
参与:
<input name="isGoing" type="checkbox" checked={this.state.isGoing} onChange={this.handleInputChange} />
</label>
<br />
<label>
来宾人数:
<input name="numberOfGuests" type="number" value={this.state.numberOfGuests} onChange={this.handleInputChange} />
</label>
</form>
)
}
}受控输入空值
在受控组件上指定 value 的 prop 会阻止用户更改输入。你指定了 value,但输入仍可编辑,则可能是你意外地将value 设置为 undefined 或 null。
代码:输入最初被锁定(只读),但在短时间延迟后变为可编辑(可写)
jsx
ReactDOM.render(<input value="hi" />, mountNode)
setTimeout(function () {
ReactDOM.render(<input value={null} />, mountNode)
}, 1000)状态提升
多个组件需要反映相同的变化数据,将共享状态提升到最近的共同父组件中去
jsx
import MyInput from './myInput'
function tryConvert(number, mode) {
if (mode === 'default') {
return number * 10
} else if (mode === 'multiply') {
return number / 10
}
}
class ShowInput extends React.Component {
constructor(props) {
super(props)
this.state = {
number: '',
mode: ''
}
}
onDefaultChange = (number) => {
this.setState({
mode: 'default',
number
})
}
onMultiplyChange = (number) => {
this.setState({
mode: 'multiply',
number
})
}
render() {
const { mode, number } = this.state
const a = mode === 'default' ? number : tryConvert(number, mode)
const b = mode === 'multiply' ? number : tryConvert(number, mode)
return (
<div>
<MyInput mode="default" number={a || ''} onNumberChange={this.onDefaultChange}></MyInput>
<MyInput mode="multiply" number={b || ''} onNumberChange={this.onMultiplyChange}></MyInput>
</div>
)
}
}
export default ShowInputjsx
const modeNames = {
default: '*1',
multiply: '*10'
}
class MyInput extends React.Component {
constructor(props) {
super(props)
}
numberChange = (e) => {
this.props.onNumberChange(e.target.value)
}
render() {
const { mode, number } = this.props
return (
<fieldset>
<legend>
{number} {modeNames[mode]}
</legend>
<input value={number} onChange={this.numberChange} />
</fieldset>
)
}
}
export default MyInput组合 vs 继承
包含关系 - slot
无法提前知晓它们子组件的具体内容
组件预留一个, children prop 来将他们的子组件传递到渲染结果。通过 JSX 嵌套,将任意组件作为子组件传递给它们。
jsx
function FancyBorder(props) {
return <div className={'FancyBorder FancyBorder-' + props.color}>{props.children}</div>
}jsx
function WelcomeDialog() {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">Welcome</h1>
<p className="Dialog-message">Thank you for visiting our spacecraft!</p>
</FancyBorder>
)
}组件预留多个,将所需内容传入 props,并使用相应的 prop。
jsx
function SplitPane(props) {
return (
<div className="SplitPane">
<div className="SplitPane-left">{props.left}</div>
<div className="SplitPane-right">{props.right}</div>
</div>
)
}
function App() {
return <SplitPane left={<Contacts />} right={<Chat />} />
}组件间复用非 UI 的功能,将其提取为一个单独的 JavaScript 模块
React 哲学
UI 划分组件层级
一个组件原则上只能负责一个功能。需要负责更多的功能,这时候就应该考虑将它拆分成更小的组件
FilterableProductTable产品列表SearchBar- 搜索框ProductTable- 表单ProductCategoryRow- 产品分类ProductRow- 产品
创建一个静态版本
应用静态版本:要编写大量代码,而不需要考虑太多交互细节
添加交互功能:考虑大量细节,而不需要编写太多代码。
通过 props 传入所需的数据,不应该使用 state 构建静态版本,state 代表了随时间会产生变化的数据
确定 UI state 最小(且完整)表示
要使你的 UI 具备交互功能,需要有触发基础数据模型改变的能力,通过state来完成。
只保留所需最小state集合,其他数据由计算产生
样例分析
- 包含所有产品的原始列表
- 用户输入的搜索词 -
state - 复选框是否选中的值 -
state - 经过搜索筛选的产品列表
判断规则 - 以下不是 state
- 是否由父组件 props传递?
- 随时间推移保持不变?
- 是否根据其他
state或props计算?
确定 state 放置位置
需要确定哪个组件能够改变这些 state,或者拥有这些 state。
筛选规则
- 找到根据这个
state进行渲染的所有组件。 - 找到他们的共同所有者(common owner)组件(在组件层级上高于所有需要该 state 的组件)。
- 该共同所有者组件或者比它层级更高的组件应该拥有该
state。 - 找不到一个合适的位置来存放该 state,就可以直接创建一个新的组件来存放该
state,并将这一新组件置于高于共同所有者组件层级的位置。
FilterableProductTable 产品列表
SearchBar- 搜索框 - 展示 复选框 和 搜索词ProductTable- 表单 - 根据props进行筛选ProductCategoryRow- 产品分类ProductRow- 产品
SearchBar 和 productTable 共同所有者 为 FilterableProductTable(存放 state)
添加反向数据流
尝试让数据反向传递:处于较低层级的表单组件更新较高层级的 FilterableProductTable 中的 state
state 只能由拥有它们的组件进行更改。FilterableProductTable需要向 SearchBar传入触发 state 改变的回调,当SearchBar 改变,触发此回调。
组件生命周期
三个状态
Mounting:已插入真实 DOM
Updating:正在被重新渲染
Unmounting:已移出真实 DOM
周期方法
| 方法 | 说明 | |
|---|---|---|
componentWillMount | 渲染前调用,在客户端也在服务端 | |
componentDidMount | 第一次渲染后调用,只在客户端 | 在此之后可以通过 this.getDOMNode()来进行访问 |
componentWillReceiveProps | 组件接收到新 prop (更新后)时被调用。 | *初始化render*时不会被调用 |
shouldComponentUpdate | 在组件接收到新props或者state时被调用 | 返回boolean值,初始化或使用forceUpdate时不被调用,true代表允许改变 |
componentWillUpdate | 组件接*收到新props或者state但**还没有 render***时被调用 | 初始化时不会被调用 |
componentDidUpdate | 组件完成更新后立即调用 | 在初始化时不会被调用。 |
componentWillUnmount | 组件从 DOM 中移除之前立刻被调用 |