近期开始入门react,对于我这样的前端码畜来讲
Q: 入门最好的方式是什么?(黑人问号脸)
A: 当然是看文档呀!!!
ps:我已经断断续续看了一星期的官方文档,总体来讲,我觉得文档写的很详细,详细的程度以致于我觉得有很多啰嗦的话(哈哈),所以一边敲案例,一边做自己的总结性的归纳。
附:大佬的对话,说的还是有道理的
jsx
1.在 JSX 当中的表达式要包含在大括号里
2.书写 JSX 的时候一般都会带上换行和缩进,这样可以增强代码的可读性
3.推荐在 JSX 代码的外面扩上一个小括号,这样可以防止 分号自动插入的 bug
4.JSX 标签是闭合式的,那么你需要在结尾处用 />, 就好像 XML/HTML 一样
//定义对象数据,给下面面类似"纯函数的组件"使用
const user = {
firstName: 'Harper',
lastName: 'Perez'
};
//jsx中调用此函数表达式
function formatName(user) {
return user.firstName + ' ' + user.lastName;
};
// jsx换行缩进,增强可读性
const element = (
<h1>
Hello, {formatName(user)}!
</h1> {/* JSX标签是闭合式的 */}
);
// 渲染元素
ReactDOM.render(
element,
document.getElementById('root')
);
5.Babel 转译器会把 JSX 转换成一个名为 React.createElement() 的方法调用。
const element = <h1 className="greeting">Hello, world!</h1>;
//两种代码的作用是完全相同
const element = React.createElement(
"h1",
{ className: "greeting" },
"Hello, world!"
);
6.JSX 的特性更接近 JavaScript 而不是 HTML , 所以 React DOM 使用 camelCase 小驼峰命名 来定义属性的名称,而不是使用 HTML 的属性名称
//class 变成了 className,而 tabindex 则对应着 tabIndex
function App(props) {
return (
<div className="Comment" tabIndex="1">
{/*
do something
*/}
</div>
);
}
元素渲染
- 元素是构成 React 应用的最小单位。
- 元素事实上只是构成组件的一个部分
- 在实际生产开发中,大多数 React 应用只会调用一次 ReactDOM.render()
- React DOM 首先会比较元素内容先后的不同,而在渲染过程中只会更新改变了的部分。
组件 & Props
组件从概念上看就像是函数,它可以接收任意的输入值(称之为“props”),并返回一个需要在页面上展示的 React 元素。
-
函数组件
function Welcome(props) { return <h1>Hello, {props.name}</h1>; }
-
类组件
class Welcome extends React.Component { render() { return <h1>Hello, {this.props.name}</h1>; } }
-
遇到的 React 元素都只是 DOM 标签,也可以是用户自定义的组件,当 React 遇到的元素是用户自定义的组件,它会将 JSX 属性作为单个对象传递给该组件,这个对象称之为“props”。
class AppComponent extends React.Component { render() { return <h1>This is class define component: {this.props.name}</h1>; } } //组件名称必须以大写字母开头。<div /> 表示一个DOM标签,但 <App /> 表示一个组件 ReactDOM.render( <AppComponent name="ReactApp" />, document.getElementById("root") );
-
通常,一个新的 React 应用程序的顶部是一个 App 组件。但是,如果要将 React 集成到现有应用程序中,则可以从下而上使用像 Button 这样的小组件作为开始,并逐渐运用到视图层的顶部。
function Welcome(props){ return <h1>Hello, {props.name}</h1> } class App extends React.Component { render() { return ( { /* 组件的返回值只能有一个根元素。这也是我们要用一个<div>来包裹所有<Welcome />元素的原因。 */ } <div> <h1>This is class define component: {this.props.name}</h1> <Welcome name="1111" /> <Welcome name="2222" /> <Welcome name="3333" /> </div> ) } } ReactDOM.render(<App name="ReactApp" />,document.getElementById("root"))
State & 生命周期
React 是非常灵活的,但它也有一个严格的规则:所有的 React 组件必须像纯函数那样使用它们的 props。何为纯函数? 字面意思就是给你什么返回什么,不关注函数内部的东西,引用函数式编程里面的一句话:所有纯函数必须遵守引用透明性,
状态与属性十分相似,但是状态是私有的,完全受控于当前组件。
封装时钟(函数组件)
//封装时钟, 理想情况下,我们写一次 Clock 然后它能更新自身,为了实现这个需求,我们需要为Clock组件添加状态
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);
类组件实现
Clock 现在被定义为一个类而不只是一个函数,使用类就允许我们使用其它特性,例如局部状态、生命周期钩子
//将函数转换为类
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
//为一个类添加局部状态State
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {
date: new Date(),
name: this.props.name //将props存入state
};
}
render() {
return (
<div>
<h1>hello,React!</h1>
<h2>this is Clock component:{this.state.date.toLocaleTimeString()}</h2>
<h3>{this.props.name}</h3>
<h3>{this.state.name}</h3>
</div>
);
}
}
ReactDOM.render(
<Clock name="这是我自定义的props:name" />,
document.getElementById("root")
);
类组件中加入 React 的生命周期方法
- 在具有许多组件的应用程序中,在销毁时释放组件所占用的资源非常重要。
- 第一次加载到 DOM 中的时候叫作
挂载
,vue 中采用$mount("#app") - OM 被移除的时候叫作
卸载
我们可以在组件类上声明特殊的方法,当组件挂载或卸载时,来运行一些代码:
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {
date: new Date(),
name: this.props.name
};
}
//生命周期钩子
componentDidMount() {
this.timerID = setInterval(() => this.tick(), 1000);
}
//生命周期钩子
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
//调用 setState() 来调度UI更新
this.setState({
date: new Date()
});
}
render() {
return (
<div>
<h1>hello,React!</h1>
<h2>this is Clock component:{this.state.date.toLocaleTimeString()}</h2>
<h3>{this.props.name}</h3>
<h3>{this.state.name}</h3>
</div>
);
}
}
ReactDOM.render(
<Clock name="这是我自定义的props:name" />,
document.getElementById("root")
);
State 正确使用
1.不要直接更新状态
应当使用 setState(),构造函数是唯一能够初始化 this.state 的地方。
2.状态更新可能是异步的
React 可以将多个 setState() 调用合并成一个调用来提高性能。this.props 和 this.state 可能是异步更新的,你不应该依靠它们的值来计算下一个状态。请使用第二种形式的 setState() 来接受一个函数而不是一个对象。 该函数将接收先前的状态作为第一个参数,将此次更新被应用时的 props 做为第二个参数:
// es6大括号解释为块代码,如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则报错
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
this.setState(function(prevState, props) {
return {
counter: prevState.counter + props.increment
};
});
3.状态更新合并
当你调用 setState() 时,React 将你提供的对象合并到当前状态。
class CommentApp extends React.Component {
constructor(props) {
super(props);
this.state = {
user: "eastboat1",
comments: [
{ id: 1, content: "这是评论1" },
{ id: 2, content: "这是评论2" },
{ id: 3, content: "这是评论3" }
]
};
}
//生命周期钩子
componentDidMount() {
//模拟异步数据修改state ,独立更新对应的state
setTimeout(() => {
this.setState({
user: "eastboat2"
});
}, 3000);
}
componentWillUnmount() {}
render() {
return (
<div>
<h2>this is my component:CommentApp</h2>
<h2>{this.state.user}</h2>
<h2>{this.props.UserName}</h2>
</div>
);
}
}
ReactDOM.render(
<CommentApp UserName="这是我自定义的props:UserName" />,
document.getElementById("root")
);
数据流向(单向流)
组件可以选择将其状态作为属性传递给其子组件:这通常被称为自顶向下或单向数据流。 任何状态始终由某些特定组件所有,并且从该状态导出的任何数据或 UI 只能影响树中下方的组件。
事件处理
通常建议在
构造函数中绑定
或使用属性初始化器语法
来避免这类
- React 事件绑定属性的命名采用驼峰式写法,而不是小写
- 如果采用 JSX 的语法你需要传入一个函数作为事件处理函数,而不是一个字符串(DOM 元素的写法)
//原生html中
<button onclick="handleChange">点击按钮</button>
//react
<button onClick={handleChange}>点击按钮</button>
- 在 React 中另一个不同是你不能使用返回 false 的方式阻止默认行为。你必须明确的使用 preventDefault
//类组件中 不能使用返回 false 的方式阻止默认行为
class App extends React.Component {
render() {
function handleClick(e) {
e.preventDefault();
console.log("The link was clicked.");
}
return (
<a href="www.baidu.com" onClick={handleClick}>
a标签
</a>
);
}
}
类的方法
- 使用 React 的时候通常你不需要使用 addEventListener 为一个已创建的 DOM 元素添加监听器。你仅仅需要在这个元素初始渲染的时候提供一个监听器。
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {
isToggleOn: true
};
this.handleClick = this.handleClick.bind(this);
}
//类方法,但是类的方法默认是不会绑定 this 的,需要上面的bind
handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? "ON" : "OFF"}
</button>
);
}
}
属性初始化器语
这种语法确保
this
绑定在 handleClick 中
class LoggingButton extends React.Component {
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() {
return <button onClick={e => this.handleClick(e)}>Click me</button>;
}
}
向事件处理程序传递参数
//二者等价
//箭头函数的方式,事件对象必须显式的进行传递
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
//bind 的方式,事件对象以及更多的参数将会被隐式的进行传递
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
通过 bind 方式向监听函数传参,在类组件中定义的监听函数,事件对象 e 要排在所传递参数的后面
class App extends React.Component {
constructor() {
super();
this.state = { name: "Hello world!" };
}
preventPop(name, e) {
//事件对象e要放在最后
e.preventDefault();
alert(name);
}
render() {
return (
<div>
<p>hello</p>
<a
href="https://reactjs.org"
onClick={this.preventPop.bind(this, this.state.name)}
>
Click
</a>
</div>
);
}
}
条件渲染
if 条件判断
使用 JavaScript 运算符 if 或者条件运算符去创建元素来表现当前的状态
//组件一
function ComponentOne() {
return <h1>this is component one</h1>;
}
//组件二
function ComponentTwo() {
return <h1>this is component two</h1>;
}
//容器组件
function App(props) {
const status = props.isLoginStatus;
if (status) {
return <ComponentOne />;
} else {
return <ComponentTwo />;
}
}
使用元素变量存储元素
function LoginButton(props) {
return <button onClick={props.onClick}>登录</button>;
}
function LogoutButton(props) {
return <button onClick={props.onClick}>登出</button>;
}
class LoginControl extends React.Component {
constructor(props) {
super(props);
this.handleLoginClick = this.handleLoginClick.bind(this);
this.handleLogoutClick = this.handleLoginClick.bind(this);
this.state = {
isLoginStatus: false
};
}
handleLoginClick() {
//登录
this.setState({
isLoginStatus: true
});
}
handleLogoutClick() {
//登出
this.setState({
isLoginStatus: false
});
}
render() {
const status = this.state.isLoginStatus;
let button; //创建变量存储组件元素
if (status) {
button = <LogoutButton onClick={this.handleLogoutClick} />;
} else {
button = <LoginButton onClick={this.handleLoginClick} />;
}
return <div>{button}</div>;
}
}
与运算符 &&
在 JavaScript 中,true && expression 总是会返回 expression, 而 false && expression 总是会返回 false。如果条件是 true,&& 右侧的元素就会被渲染,如果是 false,React 会忽略并跳过它。
const arr = ["a", "b", "c"];
function App(props) {
const list = props.list;
return <div>{list.length > 0 && <h1>这是list列表</h1>}</div>;
}
ReactDOM.render(<App list={arr} />, document.getElementById("root"));
三目运算符
//文本渲染使用
return (
<div>
The user is <b>{isLoggedIn ? "currently" : "not"}</b> logged in.
</div>
);
//组件渲染使用
return (
<div>
{isLoggedIn ? (
<LogoutButton onClick={this.handleLogoutClick} />
) : (
<LoginButton onClick={this.handleLoginClick} />
)}
</div>
);
阻止组件渲染
- 可以让 render 方法直接返回 null,而不进行任何渲染
- 在组件的 render 方法中返回 null 并不会影响组件的生命周期
function WarningBanner(props) {
if (!props.warn) {
return null;
}
return <div className="warning">Warning!</div>;
}
列表 && key
map() 函数
// map() 不会对空数组进行检测
// map() 不会改变原始数组。
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(number => number * 2);
// [2, 4, 6, 8, 10]
key 用作确定的标识
- 一个元素的 key 最好是这个元素在列表中拥有的一个独一无二的字符串。通常,我们使用来自数据 id 来作为元素的 key
- 如果列表项目的顺序可能会变化,我们不建议使用索引来用作 key 值,因为这样做会导致性能变差,还可能引起组件状态的问题
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
const list = this.props.listitem;
const ListItem = list.map((val, index, arr) => {
return <li key={index}>{val}</li>;
});
return (
<div>
<ul>{ListItem}</ul>
</div>
);
}
}
const arr = ["a", "b", "c", "d", "e"];
ReactDOM.render(<App listitem={arr} />, document.getElementById("root"));
- 元素的 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("root")
);
- key 只是在兄弟节点之间必须唯一
数组元素中使用的 key 在其兄弟节点之间应该是独一无二的。然而,它们不需要是全局唯一的。当我们生成两个不同的数组时,我们可以使用相同的 key 值:
JSX 中嵌入 map()
function ListItem(props) {
return <li>{props.value}</li>;
}
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
const list = this.props.listitem;
return (
<div>
<ul>
{/*此处是jsx中的写法*/
list.map((val, index, arr) => (
<ListItem value={val} key={index} />
))}
</ul>
</div>
);
}
}
const arr = ["a", "b", "c", "d", "e"];
ReactDOM.render(<App listitem={arr} />, document.getElementById("root"));
表单
在 React 里,HTML 表单元素的工作方式和其他的 DOM 元素有些不同,这是因为表单元素通常会保持一些内部的 state
受控组件
表单元素通常自己维护 state,并根据用户输入进行更新,而可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用 setState()来更新,两者结合起来,使得 React 的 state 成为“唯一数据源,渲染表单的 React 组件还控制着用户输入过程中表单发生的操作,这种表单输入元素就叫做“受控组件”。
class MyFormComponent extends React.Component {
constructor(props) {
super(props);
this.state = { value: "初始化state内容" };
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(e) {
this.setState({
value: e.target.value
});
}
handleSubmit(e) {
e.preventDefault();
console.log(e.target);
console.log(this.state.value);
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<input
type="text"
value={this.state.value}
onChange={this.handleChange}
/>
<input type="submit" value="提交" />
</form>
);
}
}
ReactDOM.render(<MyFormComponent />, document.getElementById("root"));
textarea 标签
在 HTML 中, textarea 元素通过其子元素定义其文本,而在 React 中,textarea 使用 value 属性代替,和上面的 input 一样绑定 value 属性
select 标签
React 并不会使用 selected 属性,而是在根 select 标签上使用 value 属性
//可以将数组传递到 value 属性中,以支持在 select 标签中选择多个选项
<select multiple={true} value={['B', 'C']}>
处理多个输入
当需要处理多个 input 元素时,我们可以给每个元素添加 name 属性,并让处理函数根据 event.target.name 的值选择要执行的操作
状态提升
多个组件需要反映相同的变化数据,这时我们建议将共享状态提升到最近的共同父组件中去
- 在 React 应用中,任何可变数据应当只有一个相对应的唯一“数据源”
- 应当依靠自上而下的数据流,而不是尝试在不同组件间同步 state。
组合 VS 继承
推荐使用组合而非继承来实现组件间的代码重用
在 React 中没有“槽”这一概念的限制,你可以将任何东西作为 props 进行传递
- 使用一个特殊的 children prop 来将他们的子组件传递到渲染结果中
function App(props) { return <div>{props.children} //渲染子组件</div>; } //组件中的子组件内容 function ParentComponent() { return ( <App> <p>这是子组件内容</p> <p>这是子组件内容</p> <p>这是子组件内容</p> </App> ); }
- 少数情况下,你可能需要在一个组件中预留出几个“洞”。这种情况下,我们可以不使用 children,而是自行约定:将所需内容传入 props,并使用相应的 prop。
function App(props) { return ( <div> <div className='CommOne'> { props.left } </div> <div className='CommTwo'> { props.right } </div> </div> ); } // 如 <leftComponent /> <rightComponent /> React 元素本质就是对象(object) function ParentComponent() { return ( <App left={ <leftComponent /> } right={ <rightComponent /> } /> ); } ``` **Props 和组合为你提供了清晰而安全地定制组件外观和行为的灵活方式。注意:组件可以接受任意 props,包括基本数据类型,React 元素以及函数**
如果你想要在组件间复用非 UI 的功能,我们建议将其提取为一个单独的 JavaScript 模块,如函数、对象或者类。组件可以直接引入(import)而无需通过 extend 继承它们
React 哲学
我们平时在公司中,都会和 UI 小姐姐(小哥哥)对接,做项目时首先拿到项目的 UI 设计稿,这时候我们就需要站在组件的维度去思考这个 UI 设计稿。
1.划分为组件层级
根据 UI 设计稿划分为组件层级,以合适的名称命名,UI(组件结构)和数据模型都会倾向于遵守相同的信息结构,所以 UI 和 JSON 数据模型一一对应
Q:如何确定应该将哪些部分划分到一个组件中呢?
A:可以将组件当作一种函数或者是对象来考虑,
根据单一功能原则来判定组件的范围。
也就是说,一个组件原则上只能负责一个功能。
2.编写静态的组件
- 先用已有的数据模型渲染一个不包含交互功能的 UI
- 最好将渲染 UI 和添加交互这两个过程分开,因为往往要编写大量代码,而不需要考虑太多交互细节,而添加交互功能时则要考虑大量细节,不需要编写太多代码
- 当你的应用比较简单时,使用自上而下的方式更方便;对于较为大型的项目来说,自下而上地构建,并同时为低层组件编写测试是更加简单的方式。
- state 代表了随时间会产生变化的数据,应当仅在实现交互时使用。所以构建应用的静态版本时,你不会用到它
3.确定所需的 state 的最小集合
通过问自己以下三个问题,你可以逐个检查相应数据是否属于 state
1.该数据是否是由父组件通过 props 传递而来的?如果是,那它应该不是 state。
2.该数据是否随时间的推移而保持不变?如果是,那它应该也不是 state。
3.你能否根据其他 state 或 props 计算出该数据的值?如果是,那它也不是 state。
4.确定 state 放置的位置
上面已经确定了应用所需的 state 的最小集合。接下来,我们需要确定哪个组件能够改变这些 state,或者说拥有这些 state。
1.找到根据这个 state 进行渲染的所有组件。
2.找到他们的共同所有者(common owner)组件(在组件层级上高于所有需要该 state 的组件)。
3.该共同所有者组件或者比它层级更高的组件应该拥有该 state。
4.如果你找不到一个合适的位置来存放该 state,就可以直接创建一个新的组件来存放该 state,并将这一新组件置。
5.于高于共同所有者组件层级的位置。
5.添加反向数据流( setState )
React 通过一种比传统的双向绑定略微繁琐的方法来实现反向数据传递。尽管如此,但这种需要显式声明的方法更有助于人们理解程序的运作方式。
我们通过 setState()函数让数据反向传递:处于较低层级的表单组件更新较高层级中的 state。
至此 react 文档基本核心内容已看完,再也不用担心被 T 掉了