양방향 데이터 바인딩을 허락하는 자식 요소를 정의하는 방법 중 가장 권고되는 방법은 아래와 같습니다.
<h3>Child</h3>
<input class="me-2"
@bind-value:get="Value"
@bind-value:set="OnInputChange"
@bind-value:event="oninput"
/>
<span>Child에서 Parent GetChildNumMethod 실행</span>
<p>Child Comp ChildNum의 현재 값 : @Value </p>
@code {
[Parameter]public int? Value { get; set; }
[Parameter]public EventCallback<int?> ValueChanged { get; set; }
private async void OnInputChange(int? value)
{
if (value == Value) return;
await ValueChanged.InvokeAsync(Value = value);
}
}
여기에서 주목해야 할 부분은 아래의 파라미터 쌍입니다.
[Parameter]public int? Value { get; set; }
[Parameter]public EventCallback<int?> ValueChanged { get; set; }
블레이저의 관습 중 하나는 아래와 같이 쌍을 이루는 두 파리미터를 부모와 양방향 데이터 바인딩을 위한 것으로 보는 것입니다.
[Parameter]public TName Name { get; set; }
[Parameter]public EventCallback<TName> NameChanged { get; set; }
양방향 바인딩
자식 요소가 위 파라미터 쌍을 보유하고 있으면, 부모 요소는 아래와 같이 @bind-{Name} 태그 속성을 통해 자신의 상태와 자식 요소의 파라미터 사이에 양방향 데이터 바인딩을 걸 수 있습니다.
@attribute [Route(Path)]
@rendermode InteractiveServer
<h3>Parent</h3>
<p class="mt-3 mb-3"> Child Comp에서 받은 숫자 : @_childNum </p>
<hr/>
<Child @bind-Value="_childNum"></Child>
@code {
public const string Path = "/parent";
private int? _childNum;
}
블레이저 프레임워크가 제공하는 요소들은 Name 에 보통 “Value” 를 쓰는데, 커스텀 요소인 경우 보다 적절한 의미를 나타내는 이름으로 변경할 수 있습니다.
[Parameter]public int? Number { get; set; }
[Parameter]public EventCallback<int?> NumberChanged { get; set; }
// ...
<Child @bind-Number="_childNum"></Child>
// ..
자식 => 부모 단방향 바인딩
부모 요소는 아래와 같이 자식으로부터 값을 읽기만 할 수 있습니다.
// ...
<Child ValueChanged="OnChildValueChanged" ></Child>
@code {
// ...
private void OnChildValueChanged(int? value)
{
if (_childNum == value) return;
_childNum = value;
}
}
양방향 방인딩이든, 단방향 바인딩이든, 자식 요소의 이벤트에 의해 부모 요소가 리렌더링되기 때문에 StateHasChanged 를 수동으로 호출하면 이중 렌더링이 됩니다. (호출하지 말아야 합니다)
파라미터에 쓰지 마라
보통은 파라미터로 노출한 속성에 직접적으로 쓰는 것을 피하는 것이 권고됩니다.
이 원칙을 Child 요소에 적용해 보면,
<h3>Child</h3>
<input class="me-2"
@bind-value:get ="_inputValue"
@bind-value:set="OnInputChange"
@bind-value:event="oninput"
/>
<span>Child에서 Parent GetChildNumMethod 실행</span>
<p>Child Comp ChildNum의 현재 값 : @_inputValue </p>
@code {
[Parameter]public int? Value { get; set; }
[Parameter]public EventCallback<int?> ValueChanged { get; set; }
// 상태 관리를 내재화
private int? _inputValue;
protected override void OnParametersSet()
{
if (Value != _inputValue) _inputValue = Value;
}
private async void OnInputChange(int? value)
{
if (value == _inputValue) return;
await ValueChanged.InvokeAsync(_inputValue = value);
}
}
리렌더링의 억제
레이저 (부모)요소를 작성하다 보면, 많은 자식 요소들을 포함할 수 있고, 자식 요소들 때문에 부모의 요소의 리렌더링 횟수가 기하 급수적으로 늘어날 수 있습니다.
보통 이러한 렌더링이 부담스러워 리렝더링을 억제하고자 하는 욕구가 생깁니다.
그런데, 사실 억제하면 안되는 경우가 더 많습니다.
질문의 코드가 그렇습니다.
부모 요소는 자신의 상태를 UI에 (즉시) 반영하도록 설계되었기 때문에, 상태의 변화는 반드시 리렌더링을 통해 UI에 반영돼야 합니다.
단순히 UI에 반영해야 할 상태가 많아서 생기는 리렌더링의 증가는 억제하면 안되겠죠. 반대로, 억제하려는 시도가 결국은 오작동으로 귀결되기도 합니다.
리렌더링 증가로 인한 비효율은 내 코드 보다는 프레임워크에서 해결하는 것이 더 바람직합니다.
실제로 리렌더링이 많아도 큰 성능 저하가 발견되지 않는 것으로 보아, 블레이저의 리렌더링 로직이 대체로 최적화된 느낌이 있습니다.