jsx
const element = <h1>Hello, world!</h1>;
这就是 JSX ,他是 JavaScrip 的一种扩展语法。我们推荐在 React 中使用这种语法来描述 UI 信息。JSX 可能会让你想起某种模板语言,但是它具有 JavaScrip 的全部能力。
JSX 表示对象
Babel 将JSX编译成 React.createElement() 调用。
下面的两个例子是是完全相同的:
const element = ( <h1 className="greeting"> Hello, world! </h1>);
const element = React.createElement( 'h1', {className: 'greeting'}, 'Hello, world!');
React 只更新必需要更新的部分
React DOM 会将元素及其子元素与之前版本逐一对比, 并只对有必要更新的 DOM 进行更新, 以达到 DOM 所需的状态。
组件(Components) 和 属性(Props)
愚人码头注:Props , 即属性(Property), 在代码中写作 props , 故可用 props 指代 properties .
函数式组件
类组件
警告:
组件名称总是以大写字母开始。
举例来说, <div /> 代表一个 DOM 标签,而 <Welcome /> 则代表一个组件,并且需要在作用域中有一个 Welcome 组件。
提取组件
不要害怕把一个组件分为多个更小的组件。
组件化思维
Props 是只读的
“纯函数”
因为它们不会试图改变它们的输入,并且对于同样的输入,始终可以得到相同的结果。所有 React 组件都必须是纯函数,并禁止修改其自身 props 。
状态(State) 和 生命周期
example:
function Clock(props) { return ( <div> <h1>Hello, world!</h1> <h2>It is {props.date.toLocaleTimeString()}.</h2> </div> ); }function tick() { ReactDOM.render( <Clock date={new Date()} />, document.getElementById('root') ); } setInterval(tick, 1000);
使用state改进
state 和 props 类似,但是它是私有的,并且由组件本身完全控制。
把函数式组件转化为类组件:
创建一个继承自
React.Component
类的 ES6 class 同名类。添加一个名为
render()
的空方法。把原函数中的所有内容移至
render()
中。在
render()
方法中使用this.props
替代props
。删除保留的空函数声明。
class Clock extends React.Component { render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this.props.date.toLocaleTimeString()}.</h2> </div> ); } }
在类组件中添加本地状态(state)
引进 类构造函数(class constructor) 初始化this.state
:
class Clock 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> ); } } ReactDOM.render( <Clock />, document.getElementById('root') );
在类中添加生命周期方法
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>It is {this.state.date.toLocaleTimeString()}.</h2> </div> ); } } ReactDOM.render( <Clock />, document.getElementById('root') );
-组件调用过程
当
<Clock />
被传入ReactDOM.render()
时,React
会调用Clock
组件的构造函数。 因为Clock
要显示的是当前时间,所以它将使用包含当前时间的对象来初始化this.state
。我们稍后会更新此状态。
2.然后 React
调用了 Clock
组件的 render()
方法。 React
从该方法返回内容中得到要显示在屏幕上的内容。然后,React
然后更新DOM
以匹配 Clock
的渲染输出。
3.当Clock
输出被插入到DOM
中时,React 调用 componentDidMount()
生命周期钩子。在该方法中,Clock
组件请求浏览器设置一个定时器来一次调用 tick()
。
4.浏览器会每隔一秒调用一次 tick()
方法。在该方法中, Clock
组件通过 setState()
方法并传递一个包含当前时间的对象来安排一个UI
的更新。通过 setState()
, React
得知了组件 state
(状态)的变化, 随即再次调用render()
方法,获取了当前应该显示的内容。 这次,render()
方法中的this.state.date
的值已经发生了改变, 从而,其输出的内容也随之改变。React
于是据此对DOM
进行更新。
5.如果通过其他操作将 Clock 组件从 DOM
中移除了, React
会调用 componentWillUnmount()
生命周期钩子, 所以计时器也会被停止。
正确地使用 State(状态)
不要直接修改 state(状态)
// 错误this.state.comment = 'Hello';
// 正确this.setState({comment: 'Hello'});
state(状态) 更新可能是异步的
React 为了优化性能,有可能会将多个 setState() 调用合并为一次更新。
因为 this.props 和 this.state 可能是异步更新的,你不能依赖他们的值计算下一个state(状态)。
例如, 以下代码可能导致 counter(计数器)更新失败:// 错误this.setState({ counter: this.state.counter + this.props.increment, });
要弥补这个问题,使用另一种 setState() 的形式,它接受一个函数而不是一个对象。这个函数将接收前一个状态作为第一个参数,应用更新时的 props 作为第二个参数:
// 正确this.setState((prevState, props) => ({ counter: prevState.counter + props.increment }));
state(状态)更新会被合并
当你调用 setState(), React 将合并你提供的对象到当前的状态中。
componentDidMount() { fetchPosts().then(response => { this.setState({ posts: response.posts }); }); fetchComments().then(response => { this.setState({ comments: response.comments }); }); }
合并是浅合并,所以 this.setState({comments}) 不会改变 this.state.posts 的值,但会完全替换this.state.comments 的值。
数据向下流动
如果把组件树想像为 props(属性) 的瀑布,所有组件的 state(状态) 就如同一个额外的水源汇入主流,且只能随着主流的方向向下流动。
处理事件
取消点击默认行为
React 中你不能通过返回 false(愚人码头注:即 return false; 语句) 来阻止默认行为。必须明确调用 preventDefault
function ActionLink() { function handleClick(e) { // e 无兼容性问题 e.preventDefault(); console.log('The link was clicked.'); } return ( <a href="#" onClick={handleClick}> Click me </a> ); }
this的绑定及事件参数传递
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button><button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
述两行代码是等价的,分别使用 arrow functions 和 Function.prototype.bind
。
条件渲染
有状态组件
它将渲染<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('root') );
使用逻辑 && 操作符的内联 if 用法
您可以 在JSX中嵌入任何表达式 ,方法是将其包裹在花括号中。这也包括 JavaScript 逻辑 &&
运算符。
使用条件操作符的内联 If-Else
render() { const isLoggedIn = this.state.isLoggedIn; return ( <div> {isLoggedIn ? ( <LogoutButton onClick={this.handleLogoutClick} /> ) : ( <LoginButton onClick={this.handleLoginClick} /> )} </div> ); }
防止组件渲染
在极少数情况下,您可能希望组件隐藏自身,即使它是由另一个组件渲染的。为此,返回 null 而不是其渲染输出。
function WarningBanner(props) { if (!props.warn) { return null; } return ( <div className="warning"> 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 ? 'Hide' : 'Show'} </button> </div> ); } } ReactDOM.render( <Page />, document.getElementById('root') );
列表(Lists) 和 键(Keys)
可以创建元素集合,并用一对大括号
{}
在 JSX 中直接将其引用即可。
const numbers = [1, 2, 3, 4, 5];const listItems = numbers.map((number) => <li>{number}</li> );
example:
function NumberList(props) { const numbers = props.numbers; const listItems = numbers.map((number) => <li>{number}</li> ); return ( <ul>{listItems}</ul> ); }const numbers = [1, 2, 3, 4, 5]; ReactDOM.render( <NumberList numbers={numbers} />, document.getElementById('root') );
当运行上述代码的时候,将会收到一个警告:a key should be provided for list items(应该为列表元素提供一个键)(愚人码头注 :CodeOpen 中没有报警告,是因为其示例中使用的是 min 版本的 React,换成非 min 版本的就可以看到)。
解决丢失 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('root') );
键(Keys)
键(Keys) 帮助 React 标识哪个项被修改、添加或者移除了。数组中的每一个元素都应该有一个唯一不变的键(Keys)来标识,
挑选 key 最好的方式是使用一个在它的同辈元素中不重复的标识字符串。多数情况你可以使用数据中的 IDs 作为 keys:
const todoItems = todos.map((todo) => <li key={todo.id}> {todo.text} </li>);
当要渲染的列表项中没有稳定的 IDs 时,你可以使用数据项的索引值作为 key 的最后选择:
const todoItems = todos.map((todo, index) => // Only do this if items have no stable IDs <li key={index}> {todo.text} </li> );
如果列表项可能被重新排序时,我们不建议使用索引作为 keys,因为这导致一定的性能问题,会很慢。
使用 keys 提取组件
错误的 key 用法
一个好的经验准则是元素中调用 map() 需要 keys 。
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') );
keys 在同辈元素中必须是唯一的
在数组中使用的 keys 必须在它们的同辈之间唯一。然而它们并不需要全局唯一。
键是React的一个内部映射,但其不会传递给组件的内部。如果你需要在组件中使用相同的值,可以明确使用一个不同名字的 prop 传入。
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使用大括号function NumberList(props) { const numbers = props.numbers; return ( <ul> {numbers.map((number) => <ListItem key={number.toString()} value={number} /> )} </ul> ); }
表单(Forms)
select 标签
在 HTML 中,<select> 创建了一个下拉列表。例如,这段 HTML 创建一个下拉的口味(flavors)列表:
<select> <option value="grapefruit">Grapefruit</option> <option value="lime">Lime</option> <option selected value="coconut">Coconut</option> <option value="mango">Mango</option></select>
注意,Coconut 选项是初始化选中的,因为它的 selected 属性。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> 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 属性可以用来实现一个受控组件。
file input 标签
<input type="file" />
在 React 中,一个 <input type =“file”/> 和一个普通的 <input /> 类似,但有一个重要的区别:它是只读的(read-only)。 (您不能以编程方式设置值。)相反,你应该使用 File API 与文件进行交互。
class FileInput extends React.Component { constructor(props) { super(props); this.handleSubmit = this.handleSubmit.bind( this ); } handleSubmit(event) { event.preventDefault(); alert( `Selected file - ${ this.fileInput.files[0].name }` ); } render() { return ( <form onSubmit={this.handleSubmit}> <label> Upload file: <input type="file" ref={input => { this.fileInput = input; }} /> </label> <br /> <button type="submit"> Submit </button> </form> ); } } ReactDOM.render( <FileInput />, document.getElementById('root') );
处理多个输入元素
当您需要处理多个受控的 input 元素时,您可以为每个元素添加一个 name 属性,并且让处理函数根据 event.target.name 的值来选择要做什么。
forExample:
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> ); } }
状态提升(Lifting State Up)
两个组件都将其值保持在本地状态中,但是我们希望弄些状态是共用的,在React中,共享 state(状态) 是通过将其移动到需要它的组件的最接近的共同祖先组件来实现的。
我们知道 props(属性) 是只读的。在 React 中,通常通过使组件“受控”的方式来解决。就像 DOM <input>一样接受一个 value 和一个 onChange prop(属性)
forExample:
handleChange(e) { // 之前是: this.setState({temperature: e.target.value}); this.props.onTemperatureChange(e.target.value);
onTemperatureChange prop(属性) 和 temperature prop(属性) 一起由父级的 Calculator 组件提供。它将通过修改自己的本地 state(状态) 来处理变更,从而通过新值重新渲染两个输入。我们将很快看到新的 Calculator 实现。
经验总结
在一个 React 应用中,对于任何可变的数据都应该循序“单一数据源”原则。通常情况下,state 首先被添加到需要它进行渲染的组件。然后,如果其它的组件也需要它,你可以提升状态到它们最近的祖先组件。你应该依赖 从上到下的数据流向 ,而不是试图在不同的组件中同步状态。
组合 VS 继承
包含
我们建议这种组件使用特别的 children prop 来直接传递 子元素到他们的输出中(好比vue的slot):
forExample:
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> ); }
然而这并不常见,有时候,在一个组件中你可能需要多个 “占位符” 。在这种情况下,你可以使用自定义的 prop(属性),而不是使用 children :
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 /> } /> ); }
React 的编程思想
该如何拆分组件呢?
其实只需要像拆分一个新方法或新对象一样的方式即可。一个常用的技巧是单一职责原则,即一个组件理想情况下只处理一件事。如果一个组件持续膨胀,就应该将其拆分为多个更小的组件中。不要重复你自己 (DRY,don’t repeat yourself)
让我们仔细分析每一个数据,弄清楚哪一个是 state(状态) 。请简单地提出有关每个数据的 3 个问题:
是否通过 props(属性) 从父级传入? 如果是这样,它可能不是 state(状态) 。
是否永远不会发生变化? 如果是这样,它可能不是 state(状态)。
是否可以由组件中其他的 state(状态) 或 props(属性) 计算得出?如果是这样,则它不是 state(状态)。
React 单向数据流在层级中自上而下进行。这样有可能不能立即判断出状态属于哪个组件。这常常是新手最难理解的一部分,试着按下面的步骤分析操作:
确定每个基于这个 state(状态) 渲染的组件。
找出公共父级组件(一个单独的组件,在组件层级中位于所有需要这个 state(状态) 的组件的上面。愚人码头注:父级组件)。
公共父级组件 或者 另一个更高级组件拥有这个 state(状态) 。
如果找不出一个拥有该 state(状态) 的合适组件,可以创建一个简单的新组件来保留这个 state(状态) ,并将其添加到公共父级组件的上层即可。
作者:琪先生_zZ
链接:https://www.jianshu.com/p/e40df01926fc