React 学习笔记(二)

Learn from React 官方文档

一、Rendering Elements

1. Rendering an Element into the DOM

<body>
<div id="root"></div>
<script type="text/babel">
const element = <h1>Hello World!</h1>;
ReactDOM.render(element,document.getElementById("root"));
</script>
</body>

2. Updating the Rendered Element

React elements 是不可改变的,一旦创建,就不可以改变它的子节点或者属性。

<body>
<div id="root"></div>
<script type="text/babel">
    function tick () {
        const element = (
            <div>
                <h1>Hello World!</h1>
                <h2>{ new Date().toLocaleTimeString() }</h2>
            </div>
        );
        ReactDOM.render(element,document.getElementById("root"));
    }
    setInterval(tick,1000);
</script>
</body>

上例每秒调用一次 tick() 函数,每次调用重新调用一次 ReactDOM.render() ,更新 React element 的内容。

React 只更新有必要更新的内容。 React DOM 把新的element和比较之前的element 比较,将不同的地方加以更新。例如打开浏览器控制台可以看到,在上例中只改变了 h2 的文本。

二、Components and Props

  • 组件将 UI 分成独立的、可重用的小块。
  • 组件和 JavaScript 函数相似。它们接受任意输入 (called "props"),返回描述所要显示的 React elements
  • 组件的返回必须是一个单独的 element 而不可以是多个,所以可以用一个 <div> 将其他元素包裹起来
  • 尽可能将一个组件分成多个更小的组件。

1. Functional and Class Components

  • way 1: Functional Components
function Welcome (props) {
        return <h1>Hello, {props.name}</h1>;
    }

接收参数:props 一个对象
返回:一个 React element

  • way 2: Class Components
class Welcome extends React.Component {
    render() {
        return <h1>Hello, {this.props.name}</h1>;
    }
}

2. Rendering a Component

<body>
<div id="root"></div>
<script type="text/babel">
function Welcome (props) {
    return <h1>Hello {props.name} !</h1>;
}
const element = <Welcome name="Sara" />;
ReactDOM.render(
    element,
    document.getElementById('root')
);
</script>
</body>
  • 当 React 遇到用户自定义的组件时,它给组件传递一个包含 JSX 属性的对象,这个对象是 props
{
    name:"Sara"
}  //props 对象
  • 上例的执行:

  • 调用 ReactDOM.render() ,插入一个<Welcome /> 组件

  • React 调用 Welcome 组件,props 对象为 {name:"Sara"}

  • Welcome组件输出 <h1>Hello Sara</h1>

  • React 更新 DOM 节点

  • 组件的名字一定要大写字母开头

3. Composing Components

组件内使用可以用另一个组件:

<body>
<div id="root"></div>
<script type="text/babel">
function Welcome (props) {
    return <h1>Hello {props.name} !</h1>;
}
function App() {
    return (
        <div>
            <Welcome name="Sara" />
            <Welcome name="John" />
            <Welcome name="Jane" />
        </div>
    );
}

ReactDOM.render(
    <App />,
    document.getElementById('root')
);
</script>
</body>

4. Props are Read-Only

React 必须遵守的一条严格的准则:
所有的 React 组件必须是 pure function,因为 props 是只读的

pure function: 函数体的执行不修改传入的参数值

三、State and Lifecycle

  • state 可以动态地改变数据。它是私有的和完全由组件控制的。
  • 使用 Class Component 定义的组件比 Functional Component 多了一些特性,就是拥有 例如:state 。
  • 生命周期钩子 (lifecycle hooks):声明在组件中,当组件 mountunmount(刚植入和刚移除)时执行的方法.
  • The componentDidMount() hook runs after the component output has been rendered to the DOM.(componentDidMount() 方法在组件的输出植入到 DOM 中的时候执行)。
  • this.propsthis.state 这些是在 React.Component 中定义的,用户自定义的组件继承了 React.Component,所以不需要重新声明。
  • 在拥有许多组件的应用中,当组件被移除时,释放组件已经占用的资源是非常重要的。

1. 时钟例子

//时钟例子:
<body>
<div id="root"></div>
<script type="text/babel">
//定义一个组件 Clock 继承了React.Component
class Clock extends React.Component {
    constructor(props) {
        super(props);
        this.state = { date:new Date() };
    } // 在constructor 中初始化 this.state

    componentDidMount() {
        this.timeId = setInterval(()=>this.tick(),1000);
    } //Lifecycle hooks (生命周期钩子) 当DOM “mount”的时候调用

    componentWillUnmount() {
        clearInterval(this.timeId);
    } //Lifecycle hooks (生命周期钩子) 当DOM “unmount”(刚被移除)的时候调用

    tick() {
        this.setState({date:new Date()});// this.setState()方法更新state的值
    }

    render() {
        return (
            <div>
                <h2>{ this.state.date.toLocaleTimeString() }</h2>
            </div>
        );//返回要渲染的DOM
    }
}

ReactDOM.render(
    <Clock />,
    document.getElementById("root")
);//Clock组件插入ID为“root”的div元素中
</script>
</body>
  • 上面的时钟代码的执行过程:
  • <Clock /> 组件传递给 ReactDOM.render(), React 调用 Clock 组件的 constructor,由于 Clock 组件需要先显示当前的时间,在 constructor 中初始化 this.state 的值。
  • React 接着调用 Clock 组件的 render() 方法,Clock 组件 render() 的输出返回要渲染的 DOM 元素。
  • 当 DOM 元素插入后,React 调用生命周期钩子 componentDidMount()。在这个方法中,Clock 组件生成一个计时器,要求浏览器每秒钟调用一次 tick() 函数。
  • tick() 方法中调用 this.setState() 方法,更新 state 的值,更新 DOM 的内容。
  • Clock 组件从 DOM 中移除时,React调用 componentWillUnmout() 方法,清除计时器。

2. 正确使用 state

  • 不要使用赋值的方法直接修改 state 的值:
this.state.comment = 'hello'; //wrong

要使用 setState()方法更新state的值

this.setState({ comment:'hello'}); //correct

只有在constructor中可以直接为this.state分配值

constructor(props) {
  super(props);
  this.state = {comment:'hello'};
}
  • state 是异步更新的
    由于 this.propsthis.state 是异步更新的,所以不应该直接使用它们来设置新的 state
this.setState({
  counter: this.state.counter + this.props.increment,
}); //wrong

使用接收一个函数作为参数的 setState()
第一个参数:prevState(state 的上一个值)
第二个参数:props

this.setState((prevState, props) => ({
  counter: prevState.counter + props.increment
}));  //correct
  • this.state 的值可以是一个对象,这个对象包含几个独立的属性,可以用多个 setState() 独立的更新每一个属性

  • 单向数据流
    一个组件可以将它的 state 值作为 props 传递给它的子组件而与外部的其他组件无关。

四、Handling Events

<body>
<div id="root"></div>
<script type="text/babel">
class ToggleBtn extends React.Component {
    constructor(props) {
        super(props);
        this.state = { isToggle: true };
        this.handleClick = this.handleClick.bind(this);
    }

    handleClick() {
        this.setState((prevState) => ({
            isToggle: !prevState.isToggle
        }));
    }

    render() {
        return(
            <button onClick={this.handleClick}>{ this.state.isToggle? "ON":"OFF" }</button> 
        );
    }
}

ReactDOM.render(
    <ToggleBtn />,
    document.getElementById("root")
);

</script>
</body>

五、Conditional Rendering

1. example 1:

有两个组件:一个是 LoginGreeting 另一个是 LogoutGreeting
在第三个组件 Greeting 中,使用了这两个组件中的其中一个,根据 Greeting 组件的 props 对象的 greet 属性值

<body>
<div id="root"></div>
<script type="text/babel">
function LoginGreeting() {
    return <div>welcome back!</div>
}

function LogoutGreeting() {
    return <div>Thankyou!</div>
}

function Greeting (props) {
    const greet = props.greet;
    if(greet) {
        return <LoginGreeting />;
    }
    else {
        return <LogoutGreeting />;
    }
}
ReactDOM.render(<Greeting greet={true}/>,document.getElementById("root"));
</script>
</body>

2. example 2: 使用元素变量

根据不同的 state 值,将不同的组件赋给一个元素变量,在另一个组件中使用变量名插入这个组件。

<body>
<div id="root"></div>
<script type="text/babel">
function LoginBtn (props) {
    return <button  onClick={props.Click}>Login</button>
}
function LogoutBtn (props) {
    return <button  onClick={props.Click}>Logout</button>
}
class LoginControl extends React.Component {
    constructor(props) {
        super(props);
        this.state = { isLogined: false };
        this.handleClick = this.handleClick.bind(this);
    }
    
    handleClick () {
        this.setState((prevState) => ({
            isLogined: !prevState.isLogined
        }));
    }

    render() {
        const loginState = this.state.isLogined;
        let button = null;
        //根据state的loginState的值将不同的组件赋给button
        if(!loginState) {
            button = <LoginBtn Click={this.handleClick} />;
        }
        else {
            button = <LogoutBtn Click={this.handleClick} />;
        }
        return (
            <div>
                {button} 
            </div>
        );
    }
}
ReactDOM.render(<LoginControl />,document.getElementById("root"));
</script>
</body>

3. example 3: 用短路与 && 操作符

只要当 && 的左操作数为假时,表达式返回假,不计算右操作数;只有当左操作数为真时,才会计算右操作数的值,返回右操作数的值。

<body>
<div id="root"></div>
<script type="text/babel">
function Mailbox (props) {
    const unreadmessage = props.unreadmessage;
    return (
        <div>
            { unreadmessage.length > 0 && 
            <h2>You have { unreadmessage.length } messages unread.</h2>}
        </div>
    );
}
const message = ['chendahong','lindamao','haha'];
ReactDOM.render(<Mailbox unreadmessage={message}/>,document.getElementById("root"));
</script>
</body>

4. example 4: condition? true : false

condition 的值为真时,返回冒号左边的表达式的值,当 condition 的值为假时,返回冒号右边的表达式的值

<body>
<div id="root"></div>
<script type="text/babel">
class Login extends React.Component {
    constructor(props) {
        super(props);
        this.state = { isLogined: false}
    }
    render() {
        const isLogined = this.state.isLogined;
        return (
            <h2>you are { isLogined? 'login':'logout' }</h2>
        );
    }
}
ReactDOM.render(<Login />,document.getElementById("root"));
</script>
</body>

也可以返回更大的表达式,但是降低了可读性:

render() {
  const isLoggedIn = this.state.isLoggedIn;
  return (
    <div>
      {isLoggedIn ? (
      //isLoggedIn 为 true 时返回 LogoutButton 组件
        <LogoutButton onClick={this.handleLogoutClick} />
      ) : (
      //isLoggedIn 为 false 时返回 LoginButton 组件
        <LoginButton onClick={this.handleLoginClick} />
      )}
    </div>
  );
}

5. example 5: 使用返回null来隐藏一个组件

<body>
<div id="root"></div>
<script type="text/babel">
function Warning (props) {
    const isWarn = props.isWarn;
    if(!isWarn) {
        return null;
    }
    else {
        return (
            <div>Be careful!</div>
        );
    }
}

class Symbol extends React.Component {
    constructor(props) {
        super(props);
        this.state={ isWarn: true };
        this.handleClick = this.handleClick.bind(this);
    }

    handleClick() {
        this.setState((prevState) => ({
            isWarn: !prevState.isWarn
        }));
    }

    render() {
        return (
            <div>
                <Warning isWarn={this.state.isWarn}/>
                <button onClick={this.handleClick}>{this.state.isWarn? 'Hide':'Show'}</button>
            </div>
        );
    }
}

ReactDOM.render(<Symbol />,document.getElementById("root"));
</script>
</body>
  • 组件返回 null 不影响组件的生命周期方法,例如 componentWillUpdatecomponentDidUpdate 依然可以被调用。

六、Lists and Keys

1. example 1:

使用 map() 函数遍历 numbers 数组,每次遍历返回一个 <li> 元素,将遍历后返回的结果数组赋给 listItems,用一个 <ul> 将结果数组包裹起来植入 DOM

<body>
<div id="root"></div>
<script type="text/babel">
const numbers = [1,2,3,4,5];
const listItems = numbers.map((number)=>
    <li>{number}</li>
);
ReactDOM.render(
    <ul>{listItems}</ul>,
    document.getElementById("root"));
</script>
</body>

2. example 2:

组件接收一个数组,返回一个列表:

<body>
<div id="root"></div>
<script type="text/babel">
function NumberList (props) {
    const numberItem = props.numbers.map((number)=>
        <li>{number}</li>
    );
    return (
        <ul>{numberItem}</ul>
    );
}

const numbers = [1,2,3,4,5];

ReactDOM.render(
    <NumberList numbers={numbers} />,
    document.getElementById("root")
);
</script>
</body>

3. example 3:

当运行上面那些代码的时候,会在控制台得到一个 warning,需要给列表元素增加 key 属性

<body>
<div id="root"></div>
<script type="text/babel">
function NumberList (props) {
    const numberItem = props.numbers.map((number)=>
        // 为每个<li> 添加 key 属性
        <li key={number.toString()}>{number}</li>
    );
    return (
        <ul>{numberItem}</ul>
    );
}

const numbers = [1,2,3,4,5];

ReactDOM.render(
    <NumberList numbers={numbers} />,
    document.getElementById("root")
);
</script>
</body>

4. example 4:

可以为数组元素对象设置一个ID属性 ,用来作为 key

<body>
<div id="root"></div>
<script type="text/babel">
function NumberList (props) {
    const numberItem = props.numbers.map((number)=>
        <li key={number.id}>{number.text}</li>
    );
    return (
        <ul>{numberItem}</ul>
    );
}

const numbers = [
    {
        id:1,
        text:'one'
    },
    {
        id:2,
        text:'two'
    }
];

ReactDOM.render(
    <NumberList numbers={numbers} />,
    document.getElementById("root")
);
</script>
</body>

如果没有ID属性作为 key ,也可以使用数组元素的索引作为 key:
(不推荐使用索引 index 作为 key,因为当数组可以重排的时候,速度会变慢)

<body>
<div id="root"></div>
<script type="text/babel">
function NumberList (props) {
    const numberItem = props.numbers.map((number,index)=>
        <li key={index}>{number}</li>
    );
    return (
        <ul>{numberItem}</ul>
    );
}

const numbers = ['one','two','three'];

ReactDOM.render(
    <NumberList numbers={numbers} />,
    document.getElementById("root")
);
</script>
</body>

5. example 5:

拆分组件的时候,要将 key 作为组件的属性,而不要把它放在组件返回的 <li> 元素中

//wrong, 这样子相当于没有设置 key,控制台仍然会有关于 key 的 warning
<body>
<div id="root"></div>
<script type="text/babel">
function NumberList (props) {
    return (
        <li key={props.value.toString()}>{props.value}</li>
    );
}

function App (props) {
    return (
        <ul>{props.numbers.map((number)=>
            <NumberList value={number} />
            )}
        </ul>
    );
}
const numbers = ['one','two','three'];

ReactDOM.render(
    <App numbers={numbers} />,
    document.getElementById("root")
);
</script>
</body>
//correct
<body>
<div id="root"></div>
<script type="text/babel">
function NumberList (props) {
    return (
        <li>{props.value}</li>
    );
}

function App (props) {
    return (
        <ul>{props.numbers.map((number)=>
            <NumberList key={number.toString()} value={number} />
            )}
        </ul>
    );
}
const numbers = ['one','two','three'];

ReactDOM.render(
    <App numbers={numbers} />,
    document.getElementById("root")
);
</script>
</body>

6. example 6:

一个数组中每个数组元素的 key 必须互不相同,但是不同数组可以有相同的 key

<body>
<div id="root"></div>
<script type="text/babel">
function App(props) {
    const sidebar = (
        <ul>
            {props.contents.map((content) =>
            //每个使用map遍历的数组元素都要有key
            <li key={content.id}>{content.title}</li>
            )}
        </ul>
    );
    const text = (
        <div>
            {props.contents.map((content) =>
            //每个使用map遍历的数组元素都要有key
            <div key={content.id}>{content.title} : {content.text} </div>
            )}
        </div>
    );
    return (
        <div>
            {sidebar}
            <hr/>
            {text}
        </div>
    );
}

const contents = [
    {
        id:1,
        title:'Hello',
        text:'Hello World!'
    },
    {
        id:2,
        title:'Welcome',
        text:'Welcome to our world!'
    }
];

ReactDOM.render(
    <App contents={contents} />,
    document.getElementById("root")
);
</script>
</body>

七、 Forms

1. example 1: Controlled Component

<input>value 值由 React 的 state 控制:

<body>
<div id="root"></div>
<script type="text/babel">
class NameForm extends React.Component {
    constructor(props) {
        super(props);
        this.state = {value:''};
        this.handleSubmit = this.handleSubmit.bind(this);
        this.handleChange = this.handleChange.bind(this);
    }

    handleSubmit(event) {
        console.log("submit: "+ this.state.value);
        event.preventDefault();// React 中要明确调用event.preventDefalut()方法来阻止提交事件的默认行为;
    }

    handleChange(event) {
        //强行将用户的输入转化为大写
        this.setState({value:event.target.value.toUpperCase()});
    }

    render() {
        return (
            <form onSubmit={this.handleSubmit}>
                <input type="text" value={this.state.value} onChange={this.handleChange} />
                <input type="submit" value="Submit" />
            </form>)
    }
}

ReactDOM.render(
    <NameForm />,
    document.getElementById("root")
);
</script>
</body>

2. example 2: textarea

<body>
<div id="root"></div>
<script type="text/babel">
class EssayForm extends React.Component {
    constructor(props) {
        super(props);
        this.state = {value:'Please input here'};
        this.handleSubmit = this.handleSubmit.bind(this);
        this.handleChange = this.handleChange.bind(this);
    }

    handleSubmit(event) {
        console.log("submit: "+ this.state.value);
        event.preventDefault();
    }

    handleChange(event) {
        this.setState({value:event.target.value});
    }

    render() {
        return (
            <form onSubmit={this.handleSubmit}>
                <textarea value={this.state.value} onChange={this.handleChange} />
                <input type="submit" value="Submit" />
            </form>)
    }
}

ReactDOM.render(
    <EssayForm />,
    document.getElementById("root")
);
</script>
</body>

3. example 3: select

在 React 中通过给 <select> 标签设置 value 值来设置选择的选项;
<select>value 值与 <option>value 值相同,则选中该 <option>
这种方法更为方便,只要更新 <select>value 值即可改变选择的选项。

<body>
<div id="root"></div>
<script type="text/babel">
class FlavorForm extends React.Component {
    constructor(props) {
        super(props);
        this.state = { value: 'apple' };
        this.handleChange = this.handleChange.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
    }

    handleChange(event) {
        this.setState({
            value: event.target.value
        });
    }

    handleSubmit(event) {
        event.preventDefault();
        console.log("you choose "+this.state.value);
    }

    render() {
        return(
            <form onSubmit={this.handleSubmit}>
                <label>
                    Pick your favorite favor:
                    <select value={this.state.value} onChange={this.handleChange}>
                        <option value="grape">grape</option>
                        <option value="apple">apple</option>
                        <option value="banana">banana</option>
                    </select>
                </label>
                <input type="submit" value="Submit" />
            </form>
        );
    }
}

ReactDOM.render(
    <FlavorForm />,
    document.getElementById("root")
);
</script>
</body>

4. example 4 :

当你需要处理多个 <input> 的时候,可以通过给每个 <input> 设置 name 属性,让事件处理函数根据 event.target.name 的值来决定做什么事情

<body>
<div id="root"></div>
<script type="text/babel">
class Reservation extends React.Component {
    constructor(props) {
        super(props);
        this.state = { 
            isGoing: true,
            guestNumber:2
        };
        this.handleChange = this.handleChange.bind(this);
    }

    handleChange(event) {
        const target = event.target;
        const value = target.type ==='checkbox'? target.checked:target.value;
        const name = target.name;
        this.setState({
            [name]: value
        });
        console.log("change");
        console.log(value);
        console.log(name);
    }

    render() {
        return(
            <form>
                <label>
                    isGoing:
                    <input type="checkbox" name="isGoing" checked={this.state.isGoing} onChange={this.handleChange}/>
                </label>
                <br />
                <label>
                    GuestNumber:
                    <input type="number" name="guestNumber" value={this.state.guestNumber} onChange={this.handleChange}/>
                </label>
            </form>
        );
    }
}

ReactDOM.render(
    <Reservation />,
    document.getElementById("root")
);
</script>
</body>

[ name ]: ES6 计算属性名

this.setState({
  [name]: value
});

在 ES5 中相当于:

var partialState = {};
partialState[name] = value;
this.setState(partialState);

八、Lifting State Up

  • 经常在几个组件中,他们的数据受同一个变化的数据的影响,这时就要把 state 提到离这些组件最近的共同的父组件中。

  • 例如下面的例子中,输入摄氏度的组件 <TemperatureInput /> 和输入华氏度的组件 <TemperatureInput /> 他们的 value 值相互影响,在摄氏度输入框输入的摄氏度值会转化为华氏度显示在华氏度的输入框,同样,输入的华氏度也会转化为摄氏度在摄氏度输入框显示。

  • 他们有共同的父组件 <Calculator/>,将 state 值设置在 <Calculator/> 中,根据 state 值的scale 属性值判断是哪个输入框在输入,由此可将另一个输入框根据华氏度和摄氏度的转换公式进行转换。

  • <BoilingVerdict> 组件根据摄氏度输入框的值显示不同的提示语,摄氏度值大于等于100,则显示:The water would boil. 摄氏度值小于100,则显示:The water would not boil.

<body>
<div id="root"></div>
<script type="text/babel">

//<BoilingVerdict>组件根据摄氏度输入框的值显示不同的提示语,
//摄氏度值大于等于100,则显示:The water would boil.
// 摄氏度值小于100,则显示:The water would not boil.
function BoilingVerdict (props) {
    if(props.celsius >= 100) {
        return (
            <div>The water would boil.</div>
        );
    }
    else {
        return (
            <div>The water would not boil.</div>
        );
    }
}

//华氏度转摄氏度
function toCelsius(temperature) {
    return (temperature - 32) * 5 / 9;  
}

//摄氏度转华氏度
function toFahrenheit(temperature) {
    return (temperature * 9 / 5 + 32);
}

//转换函数,根据参数convert,如果传入toCelsius,则转为摄氏度,
//如果传入toFahrenheit,则转为华氏度
function Convert(input,convert) {
    const temperature = parseFloat(input);
    if(Number.isNaN(temperature)) {
        return ''; //如果输入非数字,返回' ';
    }
    else {
        const output = convert(temperature);
        const rounded = Math.round(output * 1000)/1000;
        return rounded.toString();
    }
}

//温度值输入组件
class TemperatureInput extends React.Component {
    constructor(props) {
        super(props);
        this.handleChange = this.handleChange.bind(this);
    }

    handleChange(event) {
        this.props.onChange(event.target.value);
    }

    render() {
        return(
            <fieldset>
                // 显示对应的输入框名字
                <legend>This is {this.props.scale} temperature:</legend>
                <input type="text" value={this.props.value} onChange={this.handleChange}/>
            </fieldset>
        );
    }
}

class Calculator extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            scale:'c',
            value:''
        };
        this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
        this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
    }

    handleCelsiusChange(value) {
        this.setState({
            scale:'c',
            value: value
        });
    }

    handleFahrenheitChange(value) {
        this.setState({
            scale:'f',
            value: value
        });
    }

    render() {
        const scale = this.state.scale;
        const value = this.state.value;
        const celsius = scale ==='c'? value:Convert(value,toCelsius);
        const fahrenheit = scale ==='f'? value:Convert(value,toFahrenheit);
        return(
            <div>
                <TemperatureInput scale="celsius" value={celsius} onChange={this.handleCelsiusChange} />
                <TemperatureInput scale="fahrenheit" value={fahrenheit} onChange={this.handleFahrenheitChange}/>
                <BoilingVerdict celsius={celsius} />
            </div>
        );
    }

}

ReactDOM.render(
    <Calculator />,
    document.getElementById("root")
);
</script>
</body>
Temperature
Temperature

九、Composition

1. example 1:

有一些组件一开始我们不知道他们的子节点是什么的时候,可以使用 {props.children} 在使用这个组件的时候再将子节点作为 props 的一个属性传递给该组件。

function Dialog(props) {
    return(
        <div>
            {props.children}
        </div>
    );
}

function WelcomeDialog() {
    return(
      // Dialog 标签中间的 内容就作为Dialog组件的子节点
        <Dialog>
            <h1>Welcome!</h1>
            <p>Thankyou for your visit!</p>
        </Dialog>
    );
}

2. example 2:

也可以使用自定义的属性名,在组件中多处插入不同的内容:

function SplitPane(props) {
    return(
        <div>
            <div>{props.left}</div>
            <div>{props.right}</div>
        </div>
    );
}

function App() {
    return(
        // <WelcomeDialog/>和<Chat/>是两个已经定义的组件
        <SplitPane left={<WelcomeDialog/>} right={<Chat/>} />
    );
}

3. example 3:Specialization

专门化:定义一个组件,在另一个组件中通过传入属性值,将该组件具体化为一个特殊的组件。

// 无内容的对话框组件轮廓
function Dialog(props) {
    return(
        <div>
            <h1>{props.title}</h1>
            <p>{props.message}</p>
        </div>
    );
}
//设置组件的属性值,传入具体的内容,使之成为一个专门的有特殊用处的 组件
function WelcomeDialog() {
    return(
        <Dialog title="Welcome" message="Hello" />
    );
}

十、Thinking in React

  • React 的目的是使用 JavaScript 快速地构建大型的 Web Apps。
  • 使用 React 构建项目的流程如下例:
    首先由你的 app 样式图开始,将你的 app 划分为由各个组件组成的一个大组件:
app样式图
// 你的数据
[
  {category: "Sporting Goods", price: "$49.99", stocked: true, name: "Football"},
  {category: "Sporting Goods", price: "$9.99", stocked: true, name: "Baseball"},
  {category: "Sporting Goods", price: "$29.99", stocked: false, name: "Basketball"},
  {category: "Electronics", price: "$99.99", stocked: true, name: "iPod Touch"},
  {category: "Electronics", price: "$399.99", stocked: false, name: "iPhone 5"},
  {category: "Electronics", price: "$199.99", stocked: true, name: "Nexus 7"}
];

Step 1: Break The UI Into A Component Hierarchy

  • 把 UI 按层次划分成一个个组件:首先用一个个方框将你的 UI 划分成一个个组件,并给每个组件命名,根据一个组件只完成一件事的原则来划分组件。
Paste_Image.png
  • 明确每个组件的功能:
    FilterableProductTable (orange) 包含了整个样例
    SearchBar (blue) 接收所有用户输入,包括输入框的输入和复选框的勾选
    ProductTable (green) 基于用户的输入显示和过滤数据集
    ProductCategoryRow (turquoise) 显示每个分类的标题
    ProductRow (red) 显示每一种产品的信息

这样就将整个样例如下划分了层次,包含在一个组件下的将作为它的子元素:

Paste_Image.png

Step 2: Build A Static Version in React

  • 用React建立静态的样式:这个过程使用 props 从父组件传递数据给子组件,而不要使用 statestate 是用来交互的,传递动态变化的数据的。
  • 可以采用自顶向下也可以采用自底向上的顺序构建每一个组件。

Step 3: Identify The Minimal (but complete) Representation Of UI State

  • 思考本例子中的每一个数据有:

  • 原始的产品表单

  • 用户的输入文本

  • 复选框的值

  • 过滤的产品表单

  • 决定哪一项数据成为 state 思考的三个问题:

  • 它是通过父组件的 props 属性传递来的吗?如果是,它不是 state

  • 它是否在一段时间内保持不变?如果是,它不是 state

  • 你是否可以通过其他 state 或者 props 值计算出它的值?如果可以,它不是 state

  • 在本例中,原始的产品表单是通过 props 传递的,不是 state
    用户输入的文本和复选框的值是动态的随着用户任意变化的,它是 state
    过滤的产品表单可以通过用户的输入和复选框的值计算显示,不是 state
    因此,本例的 state 是用户输入的文本和复选框的值

Step 4: Identify Where Your State Should Live

  • 明确state值放置的位置:
    React 是单向数据流的;
    每一个组件都根据 state 植入某些内容;
    每个组件有一个共同的父组件,这个组件拥有 state

  • 本例中,过滤的产品表单 ProductTable 是根据 state 值计算出来的,SearchBar 的文本框值和复选框值根据 state 来显示,每个组件共同的父组件是 FilterableProductTable,因此 FilterableProductTable 拥有 state:输入的过滤文本和复选框的值。

Step 5: Add Inverse Data Flow

最后本例的代码及最后的样式见我的Github

最后的样式

十一、Typechecking With PropTypes

1. React.PropTypes

为属性设置类型:

<body>
<div id="root"></div>
<script type="text/babel">
class Hello extends React.Component {
    constructor(props) {
        super(props);
    }
    render() {
        return(
            <div>Hello {this.props.name}</div>
        );
    }
}
Hello.propTypes = {
    name:React.PropTypes.string
}
ReactDOM.render(
    <Hello name="John" />,
    document.getElementById("root")
);
</script>
</body>
  • 在上面的例子中,只有当 name 属性的类型为 string 时,才能正常显示;
  • name=123 时,不符合设置的name属性类型,所以控制台报错;

2. Requiring Single Child

使用 React.PropTypes.element 可以要求传递给组件的子元素只能有一个:

<body>
<div id="root"></div>
<script type="text/babel">
class Hello extends React.Component {
    constructor(props) {
        super(props);
    }
    render() {
        const child = this.props.children;
        return(
            <div>{child}</div>
        );
    }
}
Hello.propTypes = {
    children:React.PropTypes.element
}
function Greeting(props) {
    return(
        <Hello>
            <div>Hello!</div>
        </Hello>
    );
}
ReactDOM.render(
    <Greeting/>,
    document.getElementById("root")
);
</script>
</body>

如果传入多个子元素:

<body>
<div id="root"></div>
<script type="text/babel">
class Hello extends React.Component {
    constructor(props) {
        super(props);
    }
    render() {
        const child = this.props.children;
        return(
            <div>{child}</div>
        );
    }
}
Hello.propTypes = {
    children:React.PropTypes.element
}
function Greeting(props) {
    return(
        <Hello>
            <div>Hello!</div>
            <div>hi</div>
        </Hello>
    );
}
ReactDOM.render(
    <Greeting/>,
    document.getElementById("root")
);
</script>
</body>

控制台提示warning:

Paste_Image.png

3. Default Prop Values

通过在 defaultProps 属性中设置默认的属性值,用于确保 this.props.name 有值,如果父组件没有设置属性值的话。同样地, propTypes 也会检测 defaultProps 中属性的类型。

<body>
<div id="root"></div>
<script type="text/babel">
class Hello extends React.Component {
    constructor(props) {
        super(props);
    }
    render() {
        return(
            <div>Hello {this.props.name}</div>
        );
    }
}
Hello.defaultProps = {
    name:'jane'
}
ReactDOM.render(
    <Hello/>,
    document.getElementById("root")
);
</script>
</body>

十二、Refs and the DOM

1. Adding a Ref to a DOM Element

  • ref 属性带有一个回调函数,这个回调函数在组件 mounted 或者 unmounted 的时候执行;
  • ref 用在 html 元素时,ref 的回调函数接收拥有这个 ref 的 DOM 元素作为它的参数;
<body>
<div id="root"></div>
<script type="text/babel">
class Hello extends React.Component {
    constructor(props) {
        super(props);
        this.focus = this.focus.bind(this);
    }
    focus() {
        this.inputText.focus();
    }
    render() {
        return(
            <form>
                <input type="text" ref={(input)=>{this.inputText = input}} />
                <input type="button" onClick={this.focus} value="click me" />
            </form>
        );
    }
}
ReactDOM.render(
    <Hello/>,
    document.getElementById("root")
);
</script>
</body>
  • 可以把 ref 添加到用 class 定义的组件上
  • 不可以把 ref 添加到用 function 定义的组件上
  • 可以在一个用 function 定义的组件内为一个用 class 定义的组件添加 ref
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容

  • 原教程内容详见精益 React 学习指南,这只是我在学习过程中的一些阅读笔记,个人觉得该教程讲解深入浅出,比目前大...
    leonaxiong阅读 2,805评论 1 18
  • 本笔记基于React官方文档,当前React版本号为15.4.0。 1. 安装 1.1 尝试 开始之前可以先去co...
    Awey阅读 7,619评论 14 128
  • 最近看了一本关于学习方法论的书,强调了记笔记和坚持的重要性。这几天也刚好在学习React,所以我打算每天坚持一篇R...
    gaoer1938阅读 1,658评论 0 5
  • 深入JSX date:20170412笔记原文其实JSX是React.createElement(componen...
    gaoer1938阅读 8,026评论 2 35
  • 摘要 JSX(JavaScriptXML)提供了一种在JavaScript中编写声明式的XML的方法,使用JSX可...
    桂圆_noble阅读 1,892评论 1 3