React Native教學 Part 3.5 - 概念分析:Declarative Programming

2018/05/28 posted in  React Native comments

原生iOS和Android的開發者在初次使用React Native時,很容易感到迷惘、不適應(反之亦然),這是因為兩邊的設計理念不同,導致在開發過程中需要的思維也不相同。

React的設計貫徹Declarative Programming(聲明式編程)的思想,與其相反的是Imperative Programming (命令式編程)。這篇介紹一下兩者有甚麼分別,讓大家理解一下兩種理念在實際使用上的分別。

React的設計理念

React.js的設計理念主要有三:

  1. Declarative
  2. Component-Based
  3. Learn Once, Write Anywhere

React Design Principles

後兩者應該頗易理解的,這篇就主要講解Declarative的概念吧。

Imperative vs Declarative

定義

以下是取自Wiki的定義:

Imperative

Imperative programming is a programming paradigm that uses statements that change a program's state. (Reference)

Declarative

Declarative programming is a programming paradigm that expresses the logic of a computation without describing its control flow. (Reference)

How(如何) vs What(甚麼)

  • Imperative是告訴程式如何做(How)一些事,其實就是平日programming常做的,你寫下指示,程式一步一步跟隨你的指示做。

  • Declarative是聲明你需要甚麼(What),不用理會程式用甚麼方法、甚麼步驟做到。

React Component中的render()方法,就是聲明不同狀態(state)下,應該顯示甚麼Component(而不是親自建立一些Component然後放到view裡面)。

實際例子

SQL

Imperative

$name = []
$city = []
customers.each do |customer|
    $name.push(customer.name)
    $city.push(customer.city)
end

Declarative

SELECT Name, City FROM Customers;

jQuery vs React

Imperative

$("#btn").click(function() {
  $(this).toggleClass("highlight")
  $(this).text() === 'Add Highlight'
    ? $(this).text('Remove Highlight')
    : $(this).text('Add Highlight')
})

Declarative

<Btn
  onToggleHighlight={this.handleToggleHighlight}
  highlight={this.state.highlight}>
    {this.state.buttonText}
</Btn>

Reference

iOS/Android vs React Native

以動態建立Button作為例子:

Imperative

iOS
class ViewController: UIViewController {
    @IBOutlet weak var button: !
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let buttons = ["Button 1", "Button 2"]
        for buttonName in buttons {
            let button = UIButton(frame: CGRect(x: 100, y: 100, width: 100, height: 50))
            button.setTitle(buttonName, for: .normal)
            button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
            self.view.addSubview(button)
        }
        
    }
}
Android
public class MyActivity extends Activity {
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        LinearLayout linearLayout = new LinearLayout(this);

        String[] buttons = {"Button 1", "Button 2"};
        for ( int i = 0; i < buttons.length; i++) {
            Button button = new Button(this);
            button.setText(buttons[i]);
            button.setOnClickListener(new View.OnClickListener() {
                public void onClick(View v) {
                    // ...
                }
            });
            linearLayout.addView(button);
        }
 
        setContentView(...);
    }
}

Declarative

class SomeView extends Component {
  render() {
    const buttons = ["Button 1", "Button 2"]
    return (
      buttons.map(name =>
        <Button title={name} onPress={this.onPressButton} />
        );
    )
  }
}

效能的考慮

雖然聲明式好像很方便,但背後卻依賴程式知道你的甚麼如何做出來。不止是component在screen上面顯示出來(這當然要做到啊),最麻煩的是當state變動時,如何有效率地重新刷新一次頁面

這方面React用到Virtual DOM和自家的diff演算法,把原本O(n3)的複雜度減至O(n),詳細可看這篇文章,這裡就不詳述了。


下一篇:React Native教學 Part 4 - Props & States