React基础
渲染元素
使用ReactDOM.render()
方法来渲染元素:
ReactDOM.render( <h1>Hello, world!</h1>, // JSX代码 document.getElementById('example') // HTML DOM节点 );
完整的代码:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>Hello React!</title> <script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script> <script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script> <script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script> </head> <body> <div id="example"></div> </body> <script type="text/babel"> const element =<h1>Hello, world!</h1>; ReactDOM.render(element, document.getElementById('example')); </script> </html>
独立的文件
抽出为独立的文件:
ReactDOM.render( <h1>Hello, world!</h1>, document.getElementById('example') );
在HTML中引用:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>Hello React!</title> <script src="./react.development.js"></script> <script src="./react-dom.development.js"></script> <script src="./babel.min.js"></script> </head> <body> <div id="example"></div> </body> <script type="text/babel" src="./hello-react.js"></script> </html>
更新机制
React元素都是不可变的。更新界面的唯一办法是创建一个新的元素,
然后将它传入ReactDOM.render()
方法:
function tick() { const element = ( <div> <h1>Hello, world!</h1> <h2>现在是 {new Date().toLocaleTimeString()}.</h2> </div> ); ReactDOM.render(element, document.getElementById('example')); } setInterval(tick, 1000);
React 只会更新必要的部分。 React DOM首先会比较元素内容先后的不同, 而在渲染过程中只会更新改变了的部分。
封装内容为函数
function Clock(props) { // 函数名为`Clock`,属性作为参数传入 return ( <div> <h1>Hello, world!</h1> <h2>现在是 {props.date.toLocaleTimeString()}.</h2> </div> ); } function tick() { ReactDOM.render( <Clock date={new Date()} />, // 日期作为属性`date` document.getElementById('example') ); } setInterval(tick, 1000);
JSX
React 使用 JSX 来替代常规的 JavaScript。 JSX 是一个看起来很像 XML 的 JavaScript 语法扩展。 但它有以下优点:
- JSX 执行更快,因为它在编译为 JavaScript 代码后进行了优化。
- 它是类型安全的,在编译过程中就能发现错误。
- 使用 JSX 编写模板更加简单快速。
JSX 是在 JavaScript 内部实现的。 我们知道元素是构成 React 应用的最小单位,JSX 就是用来声明 React 当中的元素。 与浏览器的 DOM 元素不同,React 当中的元素事实上是普通的对象, React DOM 可以确保 浏览器 DOM 的数据内容与 React 元素保持一致。
由于JSX就是JavaScript,一些标识符像class
和for
不建议作为XML属性名。
作为替代,React DOM 使用className
和htmlFor
来做对应的属性。
表达式
我们可以在 JSX 中使用 JavaScript 表达式。表达式写在花括号{}
中:
<div> <h1>{1+1}</h1> </div>
在 JSX 中不能使用 if else 语句,但可以使用 conditional (三元运算) 表达式来替代。
<div> <h1>{i == 1 ? 'True!' : 'False'}</h1> </div>
样式
React 推荐使用内联样式。我们可以使用 camelCase 语法来设置内联样式。
React 会在指定元素数字后自动添加px
var myStyle = { fontSize: 100, color: '#FF0000' }; ReactDOM.render( <h1 style = {myStyle}>菜鸟教程</h1>, document.getElementById('example') );
注释
注释需要写在花括号中,实例如下:
ReactDOM.render( <div> <h1>菜鸟教程</h1> {/*注释...*/} </div>, document.getElementById('example') );
数组
JSX 允许在模板中插入数组,数组会自动展开所有成员:
var arr = [ <h1>菜鸟教程</h1>, <h2>学的不仅是技术,更是梦想!</h2>, ]; ReactDOM.render( <div>{arr}</div>, document.getElementById('example') );
封装内容为组件
封装为ES6的组件React.Component
的子类:
-
成员方法
render()
返回内容。 -
属性在成员
this.props
中。
class Clock extends React.Component { render() { return ( <div> <h1>Hello, world!</h1> <h2>现在是 {this.props.date.toLocaleTimeString()}.</h2> </div> ); } } function tick() { ReactDOM.render( <Clock date={new Date()} />, document.getElementById('example') ); } setInterval(tick, 1000);
复合组件
以通过创建多个组件来合成一个组件,即把组件的不同功能点进行分离:
function Name(props) { return <h1>网站名称:{props.name}</h1>; } function Url(props) { return <h1>网站地址:{props.url}</h1>; } function Nickname(props) { return <h1>网站小名:{props.nickname}</h1>; } function App() { return ( <div> <Name name="菜鸟教程" /> <Url url="http://www.runoob.com" /> <Nickname nickname="Runoob" /> </div> ); } ReactDOM.render(<App />, document.getElementById('example'));
React State(状态)
React 把组件看成是一个状态机(State Machines)。
React.Component
类型的组件里有一个特殊的成员state
。
通过调用成员方法setState()
,React 知道状态已经改变,
并再次调用render()
方法来确定屏幕上应当显示什么。
实例React.Component
的ES6类,在render()
方法中使用
this.state
来修改当前的时间。
添加一个类构造函数来初始化状态this.state
,
类组件应始终使用props
调用基础构造函数。
class Clock extends React.Component { constructor(props) { super(props); this.state = {date: new Date()}; } render() { return ( <div> <h1>Hello, world!</h1> <h2>现在是 {this.state.date.toLocaleTimeString()}.</h2> </div> ); } } ReactDOM.render(<Clock />, document.getElementById('example'));
组件的生命周期方法
-
当组件第一次加载到DOM中时,会初始化资源,在 React 中被称为挂载。
钩子方法
componentDidMount()
会被调用。 -
当组件生成的DOM被移除的时候,要清除资源,这在 React 中被称为卸载。
钩子方法
componentWillUnmount()
会被调用。
class Clock extends React.Component { constructor(props) { super(props); this.state = {date: new Date()}; } // 在组件输出到 DOM 后会执行`componentDidMount()`钩子, // 我们就可以在这个钩子上设置一个定时器。 // `this.timerID`为定时器的引用, componentDidMount() { this.timerID = setInterval(() => this.tick(), 1000); } // `this.timerID`为定时器的引用, // 我们可以在`componentWillUnmount()`钩子中卸载定时器。 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 />
被传递给ReactDOM.render()
时,React调用Clock
组件的构造函数。 由于Clock
需要显示当前时间,所以使用包含当前时间的对象来初始化this.state
。 稍后会更新此状态。 -
React然后调用
Clock
组件的render()
方法。React了解屏幕上应该显示什么内容, 然后React更新DOM以匹配Clock的渲染输出。 -
当
Clock
的输出插入到DOM中时,React调用componentDidMount()
生命周期钩子。 在其中,Clock 组件要求浏览器设置一个定时器,每秒钟调用一次tick()
。 -
浏览器每秒钟调用
tick()
方法。 在其中,Clock
组件通过使用包含当前时间的对象调用setState()
来调度UI更新。 通过调用setState()
,React 知道状态已经改变, 并再次调用render()
方法来确定屏幕上应当显示什么。 这一次,render()
方法中的this.state.date
将不同, 所以渲染输出将包含更新的时间,并相应地更新 DOM。 -
一旦
Clock
组件被从DOM中移除,React会调用componentWillUnmount()
, 定时器也就会被清除。
数据自顶向下流动
父组件或子组件都不能知道某个组件是有状态还是无状态, 并且它们不应该关心某组件是被定义为一个函数还是一个类。
这就是为什么状态通常被称为局部或封装。 除了拥有并设置它的组件外, 其它组件不可访问。
以下实例中FormattedDate
组件将在其属性中接收到date
值,
并且不知道它是来自Clock
状态、还是来自Clock
的属性、亦或手工输入:
function FormattedDate(props) { return <h2>现在是 {props.date.toLocaleTimeString()}.</h2>; } 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> <FormattedDate date={this.state.date} /> </div> ); } } ReactDOM.render(<Clock />, document.getElementById('example'));
这通常被称为自顶向下或单向数据流。 任何状态始终由某些特定组件所有, 并且从该状态导出的任何数据或 UI 只能影响树中下方的组件。
如果你想象一个组件树作为属性的瀑布,每个组件的状态就像一个额外的水源, 它连接在一个任意点,但也流下来。
为了表明所有组件都是真正隔离的,我们可以创建一个 App 组件,它渲染三个Clock:
function FormattedDate(props) { return <h2>现在是 {props.date.toLocaleTimeString()}.</h2>; } 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> <FormattedDate date={this.state.date} /> </div> ); } } function App() { return ( <div> <Clock /> <Clock /> <Clock /> </div> ); } ReactDOM.render(<App />, document.getElementById('example'));
以上实例中每个 Clock 组件都建立了自己的定时器并且独立更新。
在 React 应用程序中, 组件是有状态还是无状态被认为是可能随时间而变化的组件的实现细节。 我们可以在有状态组件中使用无状态组件,也可以在无状态组件中使用有状态组件。
React Props
state
和props
主要的区别在于props
是不可变的,
而state
可以根据与用户交互来改变。
容器组件需要定义state
来更新和修改数据。 而子组件只能通过props
来传递数据。
使用 Props
以下实例演示了如何在组件中使用 props:
function HelloMessage(props) { return <h1>Hello {props.name}!</h1>; } const element = <HelloMessage name="Runoob"/>; ReactDOM.render(element, document.getElementById('example'));
实例中name
属性通过props.name
来获取。
默认 Props
你可以通过组件类的defaultProps
属性为props
设置默认值,实例如下:
class HelloMessage extends React.Component { render() { return ( <h1>Hello, {this.props.name}</h1> ); } } HelloMessage.defaultProps = {name: 'Runoob'}; const element = <HelloMessage/>; ReactDOM.render(element, document.getElementById('example'));
State 和 Props
以下实例演示了如何在应用中组合使用state
和props
:
-
我们可以在父组件中设置
state
-
并通过在子组件上使用
props
将其传递到子组件上。 -
在
render()
函数中, 我们设置name
和site
来获取父组件传递过来的数据。
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 验证
React.PropTypes
在 React v15.5 版本后已经移到了prop-types
库。
<script src="https://cdn.bootcss.com/prop-types/15.6.1/prop-types.js"></script>
Props 验证使用propTypes
,它可以保证我们的应用组件被正确使用,
React.PropTypes
提供很多验证器 (validator) 来验证传入数据是否有效。
当向props
传入无效数据时,JavaScript 控制台会抛出警告。
以下实例创建一个Mytitle
组件,属性title
是必须的且是字符串,
非字符串类型会自动转换为字符串 :
React 16.4 实例
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'));
React 15.4 实例
var title = "菜鸟教程"; // var title = 123; var MyTitle = React.createClass({ propTypes: { title: React.PropTypes.string.isRequired, }, render: function() { return <h1> {this.props.title} </h1>; } }); ReactDOM.render( <MyTitle title={title} />, document.getElementById('example') );
更多验证器说明如下:
MyComponent.propTypes = { // 可以声明 prop 为指定的 JS 基本数据类型, // 默认情况,这些数据是可选的 optionalArray : React.PropTypes.array, optionalBool : React.PropTypes.bool, optionalFunc : React.PropTypes.func, optionalNumber: React.PropTypes.number, optionalObject: React.PropTypes.object, optionalString: React.PropTypes.string, // 可以被渲染的对象 numbers, strings, elements 或 array optionalNode: React.PropTypes.node, // React 元素 optionalElement: React.PropTypes.element, // 用 JS 的 instanceof 操作符声明 prop 为类的实例。 optionalMessage: React.PropTypes.instanceOf(Message), // 用 enum 来限制 prop 只接受指定的值。 optionalEnum: React.PropTypes.oneOf(['News', 'Photos']), // 可以是多个对象类型中的一个 optionalUnion: React.PropTypes.oneOfType([ React.PropTypes.string, React.PropTypes.number, React.PropTypes.instanceOf(Message) ]), // 指定类型组成的数组 optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number), // 指定类型的属性构成的对象 optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number), // 特定 shape 参数的对象 optionalObjectWithShape: React.PropTypes.shape({ color: React.PropTypes.string, fontSize: React.PropTypes.number }), // 任意类型加上 `isRequired` 来使 prop 不可空。 requiredFunc: React.PropTypes.func.isRequired, // 不可空的任意类型 requiredAny: React.PropTypes.any.isRequired, // 自定义验证器。如果验证失败需要返回一个`Error`对象。 // 不要直接使用 `console.warn` 或抛异常,因为这样 `oneOfType` 会失效。 customProp: function(props, propName, componentName) { if (!/matchme/.test(props[propName])) { return new Error('Validation failed!'); } } }
事件处理
React 元素的事件处理和 DOM 元素类似。但是有一点语法上的不同:
- React 事件绑定属性的命名采用驼峰式写法,而不是小写。
- 如果采用 JSX 的语法你需要传入一个函数作为事件处理函数, 而不是一个字符串(DOM 元素的写法)
HTML 通常写法是:
<button onclick="activateLasers()"> 激活按钮 </button>
React 中写法为:
<button onClick={activateLasers}> 激活按钮 </button>
在 React 中另一个不同是你不能使用返回false
的方式阻止默认行为,
你必须明确使用preventDefault
。
例如,通常我们在 HTML 中阻止链接默认打开一个新页面,可以这样写:
<a href="#" onclick="console.log('点击链接'); return false"> 点我 </a>
在 React 的写法为:
function ActionLink() { function handleClick(e) { e.preventDefault(); console.log('链接被点击'); } return (<a href="#" onClick={handleClick}>点我</a>); }
实例中e
是一个合成事件。
使用 React 的时候通常你不需要使用addEventListener
为一个已创建的
DOM元素添加监听器。你仅仅需要在这个元素初始渲染的时候提供一个监听器。
当你使用 ES6 class 语法来定义一个组件的时候,事件处理器会成为类的一个方法。 例如,下面的 Toggle 组件渲染一个让用户切换开关状态的按钮:
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'));
你必须谨慎对待 JSX 回调函数中的this
,类的方法默认是不会绑定this
的。
如果你忘记绑定this.handleClick
并把它传入onClick
,
当你调用这个函数的时候this
的值会是undefined
。
这并不是 React 的特殊行为;它是函数如何在 JavaScript 中运行的一部分。
通常情况下,如果你没有在方法后面添加()
,
例如onClick={this.handleClick}
,你应该为这个方法绑定this
。
如果使用 bind 让你很烦,这里有两种方式可以解决。 如果你正在使用实验性的属性初始化器语法, 你可以使用属性初始化器来正确的绑定回调函数:
class LoggingButton extends React.Component { // 这个语法确保了 `this` 绑定在 handleClick 中 // 这里只是一个测试 handleClick = () => { console.log('this is:', this); } render() { return (<button onClick={this.handleClick}>Click me</button>); } }
如果你没有使用属性初始化器语法,你可以在回调函数中使用 箭头函数:
class LoggingButton extends React.Component { handleClick() { console.log('this is:', this); } render() { // 这个语法确保了 `this` 绑定在`handleClick`中 return ( <button onClick={(e) => this.handleClick(e)}> Click me </button> ); } }
使用这个语法有个问题就是每次LoggingButton
渲染的时候都会创建一个不同的回调函数。
在大多数情况下,这没有问题。然而如果这个回调函数作为一个属性值传入低阶组件,
这些组件可能会进行额外的重新渲染。
我们通常建议在构造函数中绑定或使用属性初始化器语法来避免这类性能问题。
向事件处理程序传递参数
通常我们会为事件处理程序传递额外的参数。例如,若是id
是你要删除那一行的 id,
以下两种方式都可以向事件处理程序传递参数:
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button> <button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
上述两种方式是等价的。
上面两个例子中,参数e
作为 React 事件对象将会被作为第二个参数进行传递。
通过箭头函数的方式,事件对象必须显式的进行传递,但是通过bind
的方式,
事件对象以及更多的参数将会被隐式的进行传递。
值得注意的是,通过bind
方式向监听函数传参,在类组件中定义的监听函数,
事件对象e
要排在所传递参数的后面,例如:
class Popper extends React.Component{ constructor() { super(); this.state = {name:'Hello world!'}; } preventPop(name, e) { //事件对象e要放在最后 e.preventDefault(); alert(name); } render() { return ( <div> <p>hello</p> {/* 通过 bind() 方法传递参数。 */} <a href="https://reactjs.org" onClick={this.preventPop.bind(this,this.state.name)}> Click </a> </div> ); } }
React 条件渲染
在 React 中,你可以创建不同的组件来封装各种你需要的行为。 然后还可以根据应用的状态变化只渲染其中的一部分。
React 中的条件渲染和 JavaScript 中的一致,使用 JavaScript 操作符 if 或 条件运算符来创建表示当前状态的元素,然后让 React 根据它们来更新 UI。
先来看两个组件:
function UserGreeting(props) { return <h1>欢迎回来!</h1>; } function GuestGreeting(props) { return <h1>请先注册。</h1>; }
我们将创建一个Greeting
组件,它会根据用户是否登录来显示其中之一:
function Greeting(props) { const isLoggedIn = props.isLoggedIn; if (isLoggedIn) { return <UserGreeting />; } else { return <GuestGreeting />; } } ReactDOM.render( // 尝试修改 isLoggedIn={true}: <Greeting isLoggedIn={false} />, document.getElementById('example') );
元素变量
你可以使用变量来储存元素。它可以帮助你有条件的渲染组件的一部分, 而输出的其他部分不会更改。
在下面的例子中,我们将要创建一个名为LoginControl
的有状态的组件。
它会根据当前的状态来渲染<LoginButton />
或<LogoutButton />
,
它也将渲染前面例子中的<Greeting />
。
class LoginControl extends React.Component { constructor(props) { super(props); this.handleLoginClick = this.handleLoginClick.bind(this); this.handleLogoutClick = this.handleLogoutClick.bind(this); this.state = {isLoggedIn: false}; } handleLoginClick() { this.setState({isLoggedIn: true}); } handleLogoutClick() { this.setState({isLoggedIn: false}); } render() { const isLoggedIn = this.state.isLoggedIn; let button = null; if (isLoggedIn) { button = <LogoutButton onClick={this.handleLogoutClick} />; } else { button = <LoginButton onClick={this.handleLoginClick} />; } return ( <div> <Greeting isLoggedIn={isLoggedIn} /> {button} </div> ); } } ReactDOM.render(<LoginControl />, document.getElementById('example'));
与运算符&&
你可以通过用花括号包裹代码在 JSX 中嵌入任何表达式 ,
也包括 JavaScript 的逻辑与&&
,它可以方便地条件渲染一个元素。
function Mailbox(props) { const unreadMessages = props.unreadMessages; return ( <div> <h1>Hello!</h1> { unreadMessages.length > 0 && <h2>您有 {unreadMessages.length} 条未读信息。</h2> } </div> ); } const messages = ['React', 'Re: React', 'Re:Re: React']; ReactDOM.render( <Mailbox unreadMessages={messages} />, document.getElementById('example') );
在 JavaScript 中:
-
true && expression
总是返回expression
-
false && expression
总是返回false
因此,如果条件是true
,&&
右侧的元素就会被渲染,如果是false
,
React会忽略并跳过它。
三目运算符
条件渲染的另一种方法是使用 JavaScript 的条件运算符:
condition ? true : false。
在下面的例子中,我们用它来有条件的渲染一小段文本。
render() { const isLoggedIn = this.state.isLoggedIn; return ( The user is {isLoggedIn ? 'currently' : 'not'} logged in. ); }
同样它也可以用在较大的表达式中,虽然不太直观:
render() { const isLoggedIn = this.state.isLoggedIn; return ( <div> { isLoggedIn ? ( <LogoutButton onClick={this.handleLogoutClick} /> ) : ( <LoginButton onClick={this.handleLoginClick} /> ) } </div> ); }
阻止组件渲染
在极少数情况下,你可能希望隐藏组件,即使它被其他组件渲染。
让render()
方法返回null
而不是它的渲染结果即可实现。
在下面的例子中,<WarningBanner />
根据属性warn
的值条件渲染。
如果warn
的值是false
,则组件不会渲染:
function WarningBanner(props) { if (!props.warn) { return null; [[}]] return (<div className="warning">警告!</div>); } class Page extends React.Component { constructor(props) { super(props); this.state = {showWarning: true} this.handleToggleClick = this.handleToggleClick.bind(this); } handleToggleClick() { this.setState(prevState => ({ showWarning: !prevState.showWarning })); } render() { return ( <div> <WarningBanner warn={this.state.showWarning} /> <button onClick={this.handleToggleClick}> {this.state.showWarning ? '隐藏' : '显示'} </button> </div> ); } } ReactDOM.render(<Page />, document.getElementById('example'));
组件的render()
方法返回null
并不会影响该组件生命周期方法的回调。
例如,componentWillUpdate()
和componentDidUpdate()
依然可以被调用。
React 列表 & Keys
我们可以使用 JavaScript 的map()
方法来创建列表。
使用map()
方法遍历数组生成了一个 1 到 5 的数字列表:
const numbers = [1, 2, 3, 4, 5]; const listItems = numbers.map( (numbers) => <li>{numbers}</li> ); ReactDOM.render( <ul>{listItems}</ul>, document.getElementById('example') );
我们可以将以上实例重构成一个组件,组件接收数组参数, 每个列表元素分配一个 key ,不然会出现警告 「a key should be provided for list items」, 意思就是需要包含 key:
function NumberList(props) { const numbers = props.numbers; const listItems = numbers.map( (number) => <li key={number.toString()}>{number}</li> ); return (<ul>{listItems}</ul>); } const numbers = [1, 2, 3, 4, 5]; ReactDOM.render( <NumberList numbers={numbers} />, document.getElementById('example') );
Keys
Keys 可以在 DOM 中的某些元素被增加或删除的时候帮助 React 识别哪些元素发生了变化。因此你应当给数组中的每一个元素赋予一个确定的标识。
const numbers = [1, 2, 3, 4, 5]; const listItems = numbers.map( (number) => <li key={number.toString()}>{number}</li> );
一个元素的 key 最好是这个元素在列表中拥有的一个独一无二的字符串。 通常,我们使用来自数据的 id 作为元素的 key:
const todoItems = todos.map( (todo) => <li key={todo.id}>{todo.text}</li> );
当元素没有确定的 id 时,你可以使用他的序列号索引index
作为 key:
const todoItems = todos.map( // 只有在没有确定的 id 时使用 (todo, index) => <li key={index}>{todo.text}</li> );
如果列表可以重新排序,我们不建议使用索引来进行排序,因为这会导致渲染变得很慢。
用keys提取组件
元素的 key 只有在它和它的兄弟节点对比时才有意义。
比方说,如果你提取出一个ListItem
组件,
你应该把 key 保存在数组中的这个<ListItem />
元素上,
而不是放在ListItem
组件中的<li>
元素上。
错误的示范
function ListItem(props) { const value = props.value; return ( // 错!你不需要在这里指定key: <li key={value.toString()}> {value} </li> ); } function NumberList(props) { const numbers = props.numbers; const listItems = numbers.map( //错!元素的key应该在这里指定: (number) => <ListItem value={number} /> ); return <ul>{listItems}</ul>); } const numbers = [1, 2, 3, 4, 5]; ReactDOM.render( <NumberList numbers={numbers} />, document.getElementById('example') );
key的正确使用方式
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('example') );
当你在 map() 方法的内部调用元素时,你最好随时记得为每一个元素加上一个独一无二的 key。
元素的 key 在他的兄弟元素之间应该唯一
数组元素中使用的 key 在其兄弟之间应该是独一无二的。然而, 它们不需要是全局唯一的。当我们生成两个不同的数组时,我们可以使用相同的键。
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('example') );
key 会作为给 React 的提示,但不会传递给你的组件。 如果您的组件中需要使用和 key 相同的值,请将其作为属性传递:
const content = posts.map( (post) => <Post key={post.id} id={post.id} title={post.title} /> );
上面例子中,Post
组件可以读出props.id
,但是不能读出props.key
。
在 jsx 中嵌入 map()
在上面的例子中,我们声明了一个单独的listItems
变量并将其包含在 JSX 中:
function NumberList(props) { const numbers = props.numbers; const listItems = numbers.map( (number) => <ListItem key={number.toString()} value={number} /> ); return (<ul>{listItems}</ul>); }
JSX 允许在大括号中嵌入任何表达式,所以我们可以在map()
中这样使用:
function NumberList(props) { const numbers = props.numbers; return ( <ul> { numbers.map( (number) => <ListItem key={number.toString()} value={number} /> ) } </ul> ); }
这么做有时可以使你的代码更清晰,但有时这种风格也会被滥用。
如果一个map()
嵌套了太多层级,那你就可以提取出组件。
React 组件 API
在本章节中我们将讨论 React 组件 API。我们将讲解以下7个方法:
-
设置状态:
setState()
-
替换状态:
replaceState()
-
设置属性:
setProps()
-
替换属性:
replaceProps()
-
强制更新:
forceUpdate()
-
获取DOM节点:
findDOMNode()
-
判断组件挂载状态:
isMounted()
设置状态:setState
setState(object nextState[, function callback])
参数
-
nextState
,将要设置的新状态,该状态会和当前的state
合并 -
callback
,可选参数,回调函数。该函数会在setState
设置成功, 且组件重新渲染后调用。
合并nextState
和当前state
,并重新渲染组件。
setState
是React事件处理函数中和请求回调函数中触发UI更新的主要方法。
关于setState
-
不能在组件内部通过
this.state
修改状态,因为该状态会在调用setState()
后被替换。 -
setState()
并不会立即改变this.state
,而是创建一个即将处理的state
。setState()
并不一定是同步的,为了提升性能React会批量执行state
和DOM渲染。 -
setState()
总是会触发一次组件重绘,除非在shouldComponentUpdate()
中实现了一些条件渲染逻辑。
例中通过点击h2
标签来使得点击计数器加1:
class Counter extends React.Component{ constructor(props) { super(props); this.state = {clickCount: 0}; this.handleClick = this.handleClick.bind(this); } handleClick() { this.setState(function(state) { return {clickCount: state.clickCount + 1}; }); } render () { return ( <h2 onClick={this.handleClick}> 点我!点击次数为: {this.state.clickCount} </h2>); } } ReactDOM.render(<Counter />, document.getElementById('example'));
替换状态:replaceState
replaceState(object nextState[, function callback])
-
nextState
,将要设置的新状态,该状态会替换当前的state
。 -
callback
,可选参数,回调函数。该函数会在replaceState
设置成功, 且组件重新渲染后调用。
replaceState()
方法与setState()
类似,
但是方法只会保留nextState
中状态,原state
不在nextState
中的状态都会被删除。
设置属性:setProps
设置组件属性,并重新渲染组件。
setProps(object nextProps[, function callback])
-
nextProps
,将要设置的新属性,该状态会和当前的props
合并 -
callback
,可选参数,回调函数。该函数会在setProps
设置成功,且组件重新渲染后调用。
props
相当于组件的数据流,它总是会从父组件向下传递至所有的子组件中。
当和一个外部的JavaScript应用集成时,我们可能会需要向组件传递数据或通知
React.render()
组件需要重新渲染,可以使用setProps()
。
更新组件,我可以在节点上再次调用React.render()
,也可以通过setProps()
方法改变组件属性,触发组件重新渲染。
替换属性:replaceProps
replaceProps(object nextProps[, function callback])
-
nextProps
,将要设置的新属性,该属性会替换当前的props
。 -
callback
,可选参数,回调函数。该函数会在replaceProps
设置成功, 且组件重新渲染后调用。
replaceProps()
方法与setProps
类似,但它会删除原有props
。
强制更新:forceUpdate
forceUpdate([function callback])
-
callback
,可选参数,回调函数。该函数会在组件render()
方法调用后调用。
forceUpdate()
方法会使组件调用自身的render()
方法重新渲染组件,
组件的子组件也会调用自己的render()
。但是,组件重新渲染时,依然会读取
this.props
和this.state
,如果状态没有改变,那么React只会更新DOM。
forceUpdate()
方法适用于this.props
和this.state
之外的组件重绘
(如:修改了this.state
后),通过该方法通知React需要调用render()
。
一般来说,应该尽量避免使用forceUpdate()
,而仅从this.props
和this.state
中读取状态并由React触发render()
调用。
获取DOM节点:findDOMNode
DOMElement findDOMNode()
返回值:DOM元素DOMElement
如果组件已经挂载到DOM中,该方法返回对应的本地浏览器 DOM 元素。
当render
返回null
或false
时,this.findDOMNode()
也会返回null
。
从DOM 中读取值的时候,该方法很有用,如:获取表单字段的值和做一些 DOM 操作。
判断组件挂载状态:isMounted
bool isMounted()
-
返回值:
true
或false
,表示组件是否已挂载到DOM中
isMounted()
方法用于判断组件是否已挂载到DOM中。可以使用该方法保证了
setState()
和forceUpdate()
在异步场景下的调用不会出错。
React 组件生命周期
组件的生命周期可分成三个状态:
- Mounting(挂载):已插入真实 DOM
- Updating(更新):正在被重新渲染
- Unmounting(卸载):已移出真实 DOM
挂载
当组件实例被创建并插入 DOM 中时,其生命周期调用顺序如下:
-
constructor()
: 在 React 组件挂载之前,会调用它的构造函数。 -
getDerivedStateFromProps()
: 在调用 render 方法之前调用, 并且在初始挂载及后续更新时都会被调用。 -
render()
:render()
方法是 class 组件中唯一必须实现的方法。 -
componentDidMount()
: 在组件挂载后(插入 DOM 树中)立即调用。
render()
方法是 class 组件中唯一必须实现的方法,
其他方法可以根据自己的需要来实现。
这些方法的详细说明,可以参考官方文档。
https://zh-hans.react.dev/
更新
每当组件的state
或props
发生变化时,组件就会更新。
当组件的props
或state
发生变化时会触发更新。组件更新的生命周期调用顺序如下:
-
getDerivedStateFromProps()
: 在调用render
方法之前调用, 并且在初始挂载及后续更新时都会被调用。根据shouldComponentUpdate()
的返回值, 判断React
组件的输出是否受当前state
或props
更改的影响。 -
shouldComponentUpdate()
:当props
或state
发生变化时,shouldComponentUpdate()
会在渲染执行之前被调用。 -
render()
:render()
方法是 class 组件中唯一必须实现的方法。 -
getSnapshotBeforeUpdate()
: 在最近一次渲染输出(提交到 DOM 节点)之前调用。 -
componentDidUpdate()
: 在更新后会被立即调用。
render() 方法是 class 组件中唯一必须实现的方法,其他方法可以根据自己的需要来实现。
卸载
当组件从 DOM 中移除时会调用如下方法:
-
componentWillUnmount()
: 在组件卸载及销毁之前直接调用。
实例,每秒更新
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, Runoob!</h1> <h2>现在时间是:{this.state.date.toLocaleTimeString()}.</h2> </div> ); } } ReactDOM.render(<Clock />, document.getElementById('root'));
定时器实例
以下实例在Hello
组件加载以后,通过componentDidMount
方法设置一个定时器,
每隔100毫秒重新设置组件的透明度,并重新渲染:
class Hello extends React.Component { constructor(props) { super(props); this.state = {opacity: 1.0}; } componentDidMount() { this.timer = setInterval(function () { var opacity = this.state.opacity; opacity -= .05; if (opacity < 0.1) { opacity = 1.0; } this.setState({opacity: opacity}); }.bind(this), 100); } render () { return ( <div style={{opacity: this.state.opacity}}> Hello {this.props.name} </div> ); } } ReactDOM.render(<Hello name="world"/>, document.body);
实例:初始化state
以下实例初始化state
,setNewnumber
用于更新state
。
所有生命周期在Content
组件中。
class Button extends React.Component { constructor(props) { super(props); this.state = {data: 0}; this.setNewNumber = this.setNewNumber.bind(this); } setNewNumber() { this.setState({data: this.state.data + 1}) } render() { return ( <div> <button onClick = {this.setNewNumber}>INCREMENT</button> <Content myNumber = {this.state.data}></Content> </div> ); } } class Content extends React.Component { componentWillMount() { console.log('Component WILL MOUNT!') } componentDidMount() { console.log('Component DID MOUNT!') } componentWillReceiveProps(newProps) { console.log('Component WILL RECEIVE PROPS!') } shouldComponentUpdate(newProps, newState) { return true; } componentWillUpdate(nextProps, nextState) { console.log('Component WILL UPDATE!'); } componentDidUpdate(prevProps, prevState) { console.log('Component DID UPDATE!') } componentWillUnmount() { console.log('Component WILL UNMOUNT!') } render() { return (<div><h3>{this.props.myNumber}</h3></div>); } } ReactDOM.render( <div><Button /></div>, document.getElementById('example') );
React AJAX
React 组件的数据可以通过componentDidMount
方法中的 Ajax 来获取,
当从服务端获取数据时可以将数据存储在state
中,
再用this.setState
方法重新渲染 UI。
当使用异步加载数据时,
在组件卸载前使用componentWillUnmount
来取消未完成的请求。
使用 jQuery 完成 Ajax 请求获取 Github 用户最新 gist 共享描述:
class UserGist extends React.Component { constructor(props) { super(props); this.state = {username: '', lastGistUrl: ''}; } componentDidMount() { this.serverRequest = $.get(this.props.source, function (result) { var lastGist = result[0]; this.setState({ username: lastGist.owner.login, lastGistUrl: lastGist.html_url }); }.bind(this)); } componentWillUnmount() { this.serverRequest.abort(); } render() { return ( <div> {this.state.username} 用户最新的 Gist 共享地址: <a href={this.state.lastGistUrl}> {this.state.lastGistUrl} </a> </div> ); } } ReactDOM.render( <UserGist source="https://api.github.com/users/octocat/gists" />, document.getElementById('example') );
React 表单与事件
HTML 表单元素与 React 中的其他 DOM 元素有所不同, 因为表单元素生来就保留一些内部状态。
像<input>
,<textarea>
,和<select>
这类表单元素会维持自身状态,
并根据用户输入进行更新。但在React中,可变的状态通常保存在组件的状态属性中,
并且只能用setState()
方法进行更新。
实例:状态变化
设置输入框input
值value = {this.state.data}
。
在输入框值发生变化时我们可以更新state
。
可以使用onChange
事件来监听input
的变化,并修改state
:
class HelloMessage extends React.Component { constructor(props) { super(props); this.state = {value: 'Hello Runoob!'}; this.handleChange = this.handleChange.bind(this); } handleChange(event) { this.setState({value: event.target.value}); } render() { var value = this.state.value; return <div> <input type="text" value={value} onChange={this.handleChange} /> <h4>{value}</h4> </div>; } } ReactDOM.render(<HelloMessage />, document.getElementById('example'));
上面的代码将渲染出一个值为Hello Runoob!
的input
元素,
并通过onChange
事件响应更新用户输入的值。
实例:子组件表单
在子组件上使用表单。onChange
方法将触发state
的更新,
并将更新的值传递到子组件的输入框的value
上来重新渲染界面。
你需要在父组件通过创建事件句柄 (handleChange) ,
并作为prop
(updateStateProp) 传递到你的子组件上。
class Content extends React.Component { render() { return <div> <input type="text" value={this.props.myDataProp} onChange={this.props.updateStateProp} /> <h4>{this.props.myDataProp}</h4> </div>; } } class HelloMessage extends React.Component { constructor(props) { super(props); this.state = {value: 'Hello Runoob!'}; this.handleChange = this.handleChange.bind(this); } handleChange(event) { this.setState({value: event.target.value}); } render() { var value = this.state.value; return <div> <Content myDataProp = {value} updateStateProp = {this.handleChange}></Content> </div>; } } ReactDOM.render(<HelloMessage />, document.getElementById('example'));
Select 下拉菜单
在 React 中,不使用selected
属性,
而在根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> 选择您最喜欢的网站 <select value={this.state.value} onChange={this.handleChange}> <option value="gg">Google</option> <option value="rn">Runoob</option> <option value="tb">Taobao</option> <option value="fb">Facebook</option> </select> </label> <input type="submit" value="提交" /> </form> ); } } ReactDOM.render(<FlavorForm />, document.getElementById('example'));
多个表单
当你有处理多个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> 是否离开: <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> ); } }
React 事件
以下实例演示通过onClick
事件来修改数据:
class HelloMessage extends React.Component { constructor(props) { super(props); this.state = {value: 'Hello Runoob!'}; this.handleChange = this.handleChange.bind(this); } handleChange(event) { this.setState({value: '菜鸟教程'}) } render() { var value = this.state.value; return <div> <button onClick={this.handleChange}>点我</button> <h4>{value}</h4> </div>; } } ReactDOM.render(<HelloMessage />, document.getElementById('example'));
从子组件中更新父组件
当你需要从子组件中更新父组件的state
时,你需要在父组件通过创建事件句柄
(handleChange),并作为prop
(updateStateProp) 传递到你的子组件上。实例如下:
class Content extends React.Component { render() { return <div> <button onClick = {this.props.updateStateProp}>点我</button> <h4>{this.props.myDataProp}</h4> </div> } } class HelloMessage extends React.Component { constructor(props) { super(props); this.state = {value: 'Hello Runoob!'}; this.handleChange = this.handleChange.bind(this); } handleChange(event) { this.setState({value: '菜鸟教程'}) } render() { var value = this.state.value; return <div> <Content myDataProp = {value} updateStateProp = {this.handleChange}></Content> </div>; } } ReactDOM.render(<HelloMessage />, document.getElementById('example'));
React Refs
React 支持一种非常特殊的属性Ref
,你可以用来绑定到render()
输出的任何组件上。
这个特殊的属性允许你引用render()
返回的相应的支撑实例(backing instance
)。
这样就可以确保在任何时间总是拿到正确的实例。
绑定一个ref
属性到render
的返回值上:
<input ref="myInput" />
在其它代码中,通过this.refs
获取支撑实例:
var input = this.refs.myInput; var inputValue = input.value; var inputRect = input.getBoundingClientRect();
完整实例
你可以通过使用this
来获取当前React
组件,或使用ref
来获取组件的引用,
实例如下:
class MyComponent extends React.Component { handleClick() { // 使用原生的 DOM API 获取焦点 this.refs.myInput.focus(); } render() { // 当组件插入到 DOM 后,ref 属性添加一个组件的引用于到 this.refs return ( <div> <input type="text" ref="myInput" /> <input type="button" value="点我输入框获取焦点" onClick={this.handleClick.bind(this)} /> </div> ); } } ReactDOM.render(<MyComponent />, document.getElementById('example'));
实例中,我们获取了输入框的支撑实例的引用,子点击按钮后输入框获取焦点。
我们也可以使用getDOMNode()
方法获取DOM元素