参照React官网,基本上就是自己直译过来的,因为看着英文很别扭
React官网
Component
组件分离你的UI界面为独立的,可重复利用的模块。各模块相互独立的同时能够相互利用。在某种程度上,组件有点类似JS中的函数,他们接受任意的输入(props)并且返回描述其特性的React元素。
定义一个组件最简单的方式就是写一个js函数:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}你也可以使用ES6的
class
去定义一个组件
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
元素可以代表DOM
节点,也可以表示用户已定义的组件。
const element = <div />
class Welcome extends React.Component {
render() {
return <h1>hello {this.props.name} </h1>
}
}
const element = <Welcome name='mianmian'/>
ReactDOM.render(
element,
document.getElementById('example')
)
警告:
组件的名字首字母要大写
-
组件可以在输出时调用其他的组件,通过这样的方式,可以使一些抽象的组件变得具体。
class Welcome extends React.Component { render() { return <h1>hello {this.props.name} </h1> } } class App extends React.Component { render() { return ( <div> <Welcome name='zhuang' /> <Welcome name='weimian' /> </div> ) } } const element = <App /> ReactDOM.render( <App />, document.getElementById('example') )
提取组件
提取组件出来使得组件可重复利用,而且让代码阅读起来比较简洁,不会那么冗余。
class Welcome extends React.Component {
render() {
return <h1>hello {this.props.name} </h1>
}
}
class Old extends React.Component {
render() {
return <h1> {this.props.old} </h1>
}
}
class App extends React.Component {
render() {
return (
<div>
<Welcome name={this.props.name} />
<Old old={this.props.old} />
</div>
)
}
}
const element = <App />
ReactDOM.render(
<App name='zhuang' old='15'/>,
document.getElementById('example')
)
所有的React组件必须像纯函数一样不改变Props的值
State和Lifecycle
state
的特性跟props
有点类似,但是它是private和受组件控制的,state
只适用于组件通过class
方式定义时的情况,所以有时要将函数式组件转换为class式组件
添加本地的state到一个class中
在
render()
方法中用this.state.date
代替this.props.date
添加一个
class constructor
来初始化this.state
-
去掉
ReactDOM
中的props
属性class Operation extends React.Component { constructor(props) { super(props); this.state = {date: new Date()}; } render() { return ( <div> <h1>hello world</h1> <h2>It is {this.state.date.toLocaleTimeString()}</h2> </div> ); } } const element = <Operation /> ReactDOM.render( element, document.getElementById('example') )
生命周期
在一个组件的应用中,应释放组件里的资源
componentDidMount () {
}
componentWillUnmount () {
}
这些方法称为生命周期的钩子, componentDidMount()
钩子在组件输出在DOM上后执行,比如可以在 componentDidMount()
函数中加入一个计时器,在componentWillUnmount()
中销毁。
componentDidMount() {
this.timerID = setInterval(() => this.tick(),1000);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
最后,我们将实现tick()
方法每秒,用this.setState()
来更新state
的值
class Operation extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timeID = setInterval( () => this.tick(),1000);
}
componentWillMount() {
clearInterval(this.timeID);
}
tick() {
this.setState({date: new Date()})
}
render() {
return (
<div>
<h1>hello world</h1>
<h2>It is {this.state.date.toLocaleTimeString()}</h2>
</div>
);
}
}
const element = <Operation />
ReactDOM.render(
element,
document.getElementById('example')
)
正确使用state
不要直接改变state
的值,要使用setState()
.
// Wrong
this.state.comment = 'Hello';
// Correct
this.setState({comment: 'Hello'});
constructor
是唯一可以分配this.state
的地方
state的更新可以是异步的
因为this.props
和this.state
更新可以是异步的,所以我们不能通过他们的值来计算下个state
。
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
如果一定要计算该怎么办呢?
可以用setState()
来接收一个函数而不是一个对象。这个函数接受先前的state
作为其第一个参数,当前的props
作为第二个参数
class Operation extends React.Component {
constructor(props) {
super(props);
var date = 0;
this.state = {date: parseInt(date) + 1};
}
componentDidMount() {
this.timeID = setInterval( () => this.tick(),1000);
}
componentWillUnmount() {
clearInterval(this.timeID);
}
tick() {
this.setState((prevState, props) => ({
date:parseInt(prevState.date) + parseInt(props.increment)
}))
}
render() {
return (
<div>
<h1>hello world</h1>
<h2>It is {this.state.date}</h2>
</div>
);
}
}
const element = <Operation increment='12'/>
ReactDOM.render(
element,
document.getElementById('example')
)
问题:
为什么
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
这种方式得到是一样的结果
State Update are Merged
当你调用setState()
时,React
会合并你当前所提供的state
对象
你可以在各自调用setState()
来更新他们各自的属性。
数据流
不论是父组件还是子组件都不知道某一个组件的状态,他们也没有关心组件定义的方式。因此state经常被称为是本地的或者是封闭的。
一个组件可以选择传递其state特性像props给其子组件:
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
这同样可以工作在已定义的组件中:
<FormattedDate date={this.state.date} />
该组件将会接受一个date
在它的props
中即使不知道date
来自哪里
function FormattedDate(props) {
return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}
这样的方式通常称之为"由上到下"或者单向数据流。任何state
总是被一些特定的组件包含着,任何来源于state
的数据或UI只能影响在他们所在的组件内部。
如果你把组件树当作是props
的瀑布,每个组件的state
就像多出来的水源,来源于任意的地方但以同样的方式流动。
为了展示所有的组件都是独立的,我们可以写一个App
组件,来渲染三个<clock>
function App() {
return (
<div>
<Clock />
<Clock />
<Clock />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
每个clock
开始各自的计时器,各自更新。
在React app
中,你可以将一个无状态的组件包裹在一个有状态的组件里,反之亦然。
处理事件
用React
元素处理事件跟在DOM
上处理事件很相似,不过有一些语法的区别
React
事件命名运用的是驼峰法,而不是小写-
运用
Jsx
你通过一个函数作为事件处理器,而不是一个字符串<button onclick="activateLasers()"> Activate Lasers </button> // slightly different in React: <button onClick={activateLasers}> Activate Lasers </button>
当你使用React
时你一般不需要调用addEventListener
添加对DOM
元素的监听,而是在最初渲染元素的时候添加一个监听器。
条件渲染
`React`中条件渲染跟`javascript`工作的情况一样,
class Operation extends React.Component {
constructor(props) {
super(props);
}
render() {
const isOk = this.props.isOk;
if(isOk) {
return <Add />;
}
return <FalseReturn />;
}
}
class Add extends Component {
render() {
return (<h2>ok</h2>)
}
}
class FalseReturn extends Component {
render() {
return (<h1>no</h1>)
}
}
const element = <Operation isOk='true'/>
ReactDOM.render(
element,
document.getElementById('example')
)
表格
受控组件
在html中
,表格元素例如<input>
,<textarea>
,<select>
根据输入的值维持自身的state
值,在React
中,state
保持原有组件的state
属性,只通过setState()
来更新。
我们通过使React state
变成单一数据源的形式结合上述两种方式。接着React
组件会在渲染表格的同时控制表格中的用户输入。这样,表格元素的值受React
控制,因此就叫受控组件
例如,如果我们想要在提交的时候记录名字,我们就可以将一个表格作为一个受控组件:
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('A name was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
由于value
的属性被安置在我们的表格元素中,展示的value
就永远都是this.state.value
,这就构成了React
的数据源。由于handleChange
在每次点击事件更新state
时工作,展示的value
也会随用户的输入更新。
有了受控组件,每个state
变化将会与一个处理器函数联系起来。这样会使得修改数据和是数据生效更加直接。比如,
handleChange(event) {
this.setState({value: event.target.value.toUpperCase()});
}
The textarea Tag
在html
中,一个<textarea>
元素定义其文本是通过其孩子的形式:
<textarea>
Hello there, this is some text in a text area
</textarea>
在React
中,一个<textarea>
使用的是value
属性。通过这样的方式,一个表格使用<textarea>
跟使用单行输入相似。
class EssayForm extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 'Please write an essay about your favorite DOM element.'
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('An essay was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<textarea value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
The select Tag
在html
中,<select>
创造了一个下拉框,如:
<select>
<option value="grapefruit">Grapefruit</option>
<option value="lime">Lime</option>
<option selected value="coconut">Coconut</option>
<option value="mango">Mango</option>
</select>
在React中,是在根select
标签中使用 value
,这种方式在受控组件中更方便,因为你只需要在一个地方更新,比如:
class FlavorForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: 'coconut'};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('Your favorite flavor is: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Pick your favorite La Croix flavor:
<select value={this.state.value} onChange={this.handleChange}>
<option value="grapefruit">Grapefruit</option>
<option value="lime">Lime</option>
<option value="coconut">Coconut</option>
<option value="mango">Mango</option>
</select>
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
总的来说,<input type="text">
,<textarea>
,<select>
工作的原理很相似,都是接受一个value属性去实现一个受控组件。
处理多输入
当你需要处理多输入受控input
元素时,你可以添加一个name
属性给每一个元素,让处理器函数决定根据event.target.name
的值来操作。
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.type === 'checkbox' ? target.checked :target.value;
const name = target.name;
this.setState({
[name]: value
});
}
render() {
return (
<form>
<label>
Is going:
<input
name="isGoing"
type="checkbox"
checked={this.state.isGoing}
onChange={this.handleInputChange} />
</label>
<br />
<label>
Number of guests:
<input
name="numberOfGuests"
type="number"
value={this.state.numberOfGuests}
onChange={this.handleInputChange} />
</label>
</form>
);
}
}
Refs和DOM
在一般的数据流中,props
是父组件与子组件交流的唯一方式。为了修改子组件,你要用新的props
重新渲染一次,尽管如此,这里还有一些其他的情况你需要在数据流之外以命令式的方式改变子组件。这个被修改的孩子可以是React
组件中的一个例子,又或者是一个DOM
元素,对于这两种情况,React
提供了一种方式去获取。
什么情况使用Refs
- 聚焦,文本框选择,媒体重播
- 触发命令
- 与第三方DOM库交流
避免将refs用于任何可以声明的事件,例如,不要暴露
open()
和close()
方法在一个Dialog
组件中,而是传递一个isOpen
属性值给它。
添加一个Ref给一个DOM元素
React
支持一个你可以接触到任何组件的特殊属性。ref
属性携带一个返回函数.
当ref
属性运用在HTML
元素是,ref
回调函数接受一个潜在的DOM
元素作为其参数。通过ref
回调函数来设置一个class
性质对于DOM元素是一种通用的方式。还有一种更好的方式是在ref
的回调函数设置属性。
ref = {input => this.textInput = input}
ref和函数式组件
在没有实例的情况下,你可能会使用ref
属性在一个函数式组件中
function MyFunctionalComponent() {
return <input />;
}
class Parent extends React.Component {
render() {
// This will *not* work!
return (
<MyFunctionalComponent
ref={(input) => { this.textInput = input; }} />
);
}
}
如果你需要一个ref
你应该转化组件为class
形式。就跟当你需要使用生命周期或者state
的时候转化组件为class
形式一样。
尽管如此,你可以在一个函数式组件中使用ref
属性只要你参考DOM元素或者class组件
function CustomTextInput(props) {
// textInput must be declared here so the ref callback can refer to it
let textInput = null;
function handleClick() {
textInput.focus();
}
return (
<div>
<input
type="text"
ref={(input) => { textInput = input; }} />
<input
type="button"
value="Focus the text input"
onClick={handleClick}
/>
</div>
);
}
不要过度使用refs
你会倾向于在应用里使用refs。如果是这种情况,花些时间想一想在组件的层级里state应该在哪里。通常,拥有state合适的地方应该在层级的高处
警告
如果ref
回调函数被定义为一个内部函数,在一次更新中他会被调用两次,第一次是null
,再一次是DOM
元素。你可以通过定义ref
回调函数作为一个class
的绑定方法,但是大部分情况下无关紧要
非受控组件
在大多数情况下,我们推荐使用受控组件来实现表格。在受控组件中,表格数据受React
组件处理。另外一种选择是非受控组件,表格数据受DOM
自身处理
在写一个非受控组件时,你不用写一个事件处理器为state
的更新,你可以使用ref
从DOM
中获取表格数据
例子:
class NameForm extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
alert('A name was submitted: ' + this.input.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" ref={(input) => this.input = input} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
自从非受控组件保持了DOM
的数据源后,会使得受控组件与非受控组件的交流变得容易。这也会使代码变得简洁。除此之外,你应该常常使用受控组件
Default Values
在React
渲染的生命周期中,form
元素中的value
属性将会被覆盖在DOM
里。有了非受控组件,你可以使用React
去初始化一个值,但是让随后的更新不受控制,为了处理这种情况,你可以特征化一个defaultValue
属性而不是value
属性。
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input
defaultValue="Bob"
type="text"
ref={(input) => this.input = input} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
像这样,<input type='checkbox'>
和<input type='radio'>
支持defaultChecked
,<select>
支持defaultValue
容器
一些组件不能提前知道他们的孩子,我们推荐使用一些特殊的children属性直接输出子元素
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>
);
}